Skip to main content

box3d_rust/body/
lifecycle.rs

1// Body create/destroy and island helpers from body.c.
2//
3// SPDX-FileCopyrightText: 2025 Erin Catto
4// SPDX-License-Identifier: MIT
5
6use super::{body_flags, Body, BodySim, IDENTITY_BODY_STATE};
7use crate::constants::huge;
8use crate::core::{NULL_INDEX, SECRET_COOKIE};
9use crate::id::{BodyId, NULL_BODY_ID};
10use crate::island::{create_island, destroy_island, validate_island};
11use crate::math_functions::{
12    is_valid_float, is_valid_position, is_valid_quat, is_valid_vec3, length, WorldTransform,
13};
14use crate::solver_set::{
15    destroy_solver_set, wake_solver_set, SolverSet, AWAKE_SET, DISABLED_SET, FIRST_SLEEPING_SET,
16    STATIC_SET,
17};
18use crate::types::BodyType;
19use crate::world::World;
20
21/// Resolve a BodyId to the body index. (b3GetBodyFullId)
22pub fn get_body_full_id(world: &World, body_id: BodyId) -> i32 {
23    debug_assert!(body_is_valid(world, body_id));
24    body_id.index1 - 1
25}
26
27/// Create a BodyId from a raw body index. (b3MakeBodyId)
28pub fn make_body_id(world: &World, body_index: i32) -> BodyId {
29    let body = &world.bodies[body_index as usize];
30    BodyId {
31        index1: body_index + 1,
32        world0: world.world_id,
33        generation: body.generation,
34    }
35}
36
37/// Body identifier validation. (b3Body_IsValid — world registry checks collapse
38/// to the world argument)
39pub fn body_is_valid(world: &World, id: BodyId) -> bool {
40    if id.index1 < 1 || (world.bodies.len() as i32) < id.index1 {
41        return false;
42    }
43
44    let body = &world.bodies[(id.index1 - 1) as usize];
45    if body.set_index == NULL_INDEX {
46        return false;
47    }
48
49    debug_assert!(body.local_index != NULL_INDEX);
50
51    if body.generation != id.generation {
52        return false;
53    }
54
55    true
56}
57
58/// Quick transform lookup. (b3GetBodyTransformQuick)
59pub fn get_body_transform_quick(world: &World, body: &Body) -> WorldTransform {
60    world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize].transform
61}
62
63/// Transform by body index. (b3GetBodyTransform)
64pub fn get_body_transform(world: &World, body_index: i32) -> WorldTransform {
65    get_body_transform_quick(world, &world.bodies[body_index as usize])
66}
67
68/// (b3GetBodySim)
69pub fn get_body_sim(world: &World, body_index: i32) -> &BodySim {
70    let body = &world.bodies[body_index as usize];
71    &world.solver_sets[body.set_index as usize].body_sims[body.local_index as usize]
72}
73
74/// (b3GetBodySim) mutable
75pub fn get_body_sim_mut(world: &mut World, body_index: i32) -> &mut BodySim {
76    let set_index = world.bodies[body_index as usize].set_index;
77    let local_index = world.bodies[body_index as usize].local_index;
78    &mut world.solver_sets[set_index as usize].body_sims[local_index as usize]
79}
80
81/// (b3GetBodyState) — None when the body is not in the awake set.
82pub fn get_body_state_index(world: &World, body_index: i32) -> Option<i32> {
83    let body = &world.bodies[body_index as usize];
84    if body.set_index == AWAKE_SET {
85        Some(body.local_index)
86    } else {
87        None
88    }
89}
90
91/// Sync non-transient flags from Body onto BodySim / BodyState. (b3SyncBodyFlags)
92pub fn sync_body_flags(world: &mut World, body_index: i32) {
93    let flags = world.bodies[body_index as usize].flags & !body_flags::BODY_TRANSIENT_FLAGS;
94    {
95        let sim = get_body_sim_mut(world, body_index);
96        sim.flags = flags;
97    }
98    if let Some(local_index) = get_body_state_index(world, body_index) {
99        world.solver_sets[AWAKE_SET as usize].body_states[local_index as usize].flags = flags;
100    }
101}
102
103/// Joint `collide_connected` override. (b3ShouldBodiesCollide)
104pub fn should_bodies_collide(world: &World, body_id_a: i32, body_id_b: i32) -> bool {
105    let body_a = &world.bodies[body_id_a as usize];
106    let body_b = &world.bodies[body_id_b as usize];
107
108    let (mut joint_key, other_body_id) = if body_a.joint_count < body_b.joint_count {
109        (body_a.head_joint_key, body_b.id)
110    } else {
111        (body_b.head_joint_key, body_a.id)
112    };
113
114    while joint_key != NULL_INDEX {
115        let joint_id = joint_key >> 1;
116        let edge_index = joint_key & 1;
117        let other_edge_index = edge_index ^ 1;
118        let joint = &world.joints[joint_id as usize];
119        if !joint.collide_connected
120            && joint.edges[other_edge_index as usize].body_id == other_body_id
121        {
122            return false;
123        }
124        joint_key = joint.edges[edge_index as usize].next_key;
125    }
126
127    true
128}
129
130/// (static b3CreateIslandForBody)
131pub(crate) fn create_island_for_body(world: &mut World, set_index: i32, body_index: i32) {
132    debug_assert!(world.bodies[body_index as usize].island_id == NULL_INDEX);
133    debug_assert!(set_index != DISABLED_SET);
134
135    let island_id = create_island(world, set_index);
136    world.islands[island_id as usize].bodies.push(body_index);
137    let body = &mut world.bodies[body_index as usize];
138    body.island_id = island_id;
139    body.island_index = 0;
140
141    validate_island(world, island_id);
142}
143
144/// (static b3RemoveBodyFromIsland)
145pub(crate) fn remove_body_from_island(world: &mut World, body_index: i32) {
146    let (island_id, island_index) = {
147        let body = &world.bodies[body_index as usize];
148        (body.island_id, body.island_index)
149    };
150    if island_id == NULL_INDEX {
151        debug_assert!(island_index == NULL_INDEX);
152        return;
153    }
154
155    {
156        let local_index = island_index;
157        let last = world.islands[island_id as usize].bodies.len() - 1;
158        let moved_body_id = world.islands[island_id as usize].bodies[last];
159        world.islands[island_id as usize].bodies[local_index as usize] = moved_body_id;
160        debug_assert!(world.bodies[moved_body_id as usize].island_index == last as i32);
161        world.bodies[moved_body_id as usize].island_index = local_index;
162        world.islands[island_id as usize].bodies.pop();
163    }
164
165    if world.islands[island_id as usize].bodies.is_empty() {
166        debug_assert!(world.islands[island_id as usize].contacts.is_empty());
167        debug_assert!(world.islands[island_id as usize].joints.is_empty());
168        destroy_island(world, island_id);
169    } else {
170        validate_island(world, island_id);
171    }
172
173    let body = &mut world.bodies[body_index as usize];
174    body.island_id = NULL_INDEX;
175    body.island_index = NULL_INDEX;
176}
177
178/// True when the body is in the awake set. (b3IsBodyAwake)
179pub fn is_body_awake(world: &World, body_index: i32) -> bool {
180    world.bodies[body_index as usize].set_index == AWAKE_SET
181}
182
183/// Wake a sleeping body. (b3WakeBody)
184pub fn wake_body(world: &mut World, body_index: i32) -> bool {
185    let set_index = world.bodies[body_index as usize].set_index;
186    if set_index >= FIRST_SLEEPING_SET {
187        wake_solver_set(world, set_index);
188        world.validate_solver_sets();
189        return true;
190    }
191    false
192}
193
194/// Wake with world lock. (b3WakeBodyWithLock)
195pub fn wake_body_with_lock(world: &mut World, body_index: i32) -> bool {
196    debug_assert!(!world.locked);
197    world.locked = true;
198    let woke = wake_body(world, body_index);
199    world.locked = false;
200    woke
201}
202
203/// Create a rigid body given a definition. (b3CreateBody)
204///
205/// C resolves the world from a WorldId registry; Rust takes `&mut World`.
206pub fn create_body(world: &mut World, def: &crate::types::BodyDef) -> BodyId {
207    debug_assert!(def.internal_value == SECRET_COOKIE);
208    debug_assert!(is_valid_position(def.position));
209    debug_assert!(is_valid_quat(def.rotation));
210    debug_assert!(is_valid_vec3(def.linear_velocity));
211    debug_assert!(is_valid_vec3(def.angular_velocity));
212    debug_assert!(is_valid_float(def.linear_damping) && def.linear_damping >= 0.0);
213    debug_assert!(is_valid_float(def.angular_damping) && def.angular_damping >= 0.0);
214    debug_assert!(is_valid_float(def.sleep_threshold) && def.sleep_threshold >= 0.0);
215    debug_assert!(is_valid_float(def.gravity_scale));
216
217    debug_assert!(!world.locked);
218    if world.locked {
219        return NULL_BODY_ID;
220    }
221
222    world.locked = true;
223
224    let is_awake = (def.is_awake || !def.enable_sleep) && def.is_enabled;
225
226    // determine the solver set
227    let set_id;
228    if !def.is_enabled {
229        set_id = DISABLED_SET;
230    } else if def.type_ == BodyType::Static {
231        set_id = STATIC_SET;
232    } else if is_awake {
233        set_id = AWAKE_SET;
234    } else {
235        // new set for a sleeping body in its own island
236        set_id = world.solver_set_id_pool.alloc_id();
237        if set_id == world.solver_sets.len() as i32 {
238            world.solver_sets.push(SolverSet::default());
239        } else {
240            debug_assert!(world.solver_sets[set_id as usize].set_index == NULL_INDEX);
241        }
242        world.solver_sets[set_id as usize].set_index = set_id;
243    }
244
245    debug_assert!(0 <= set_id && set_id < world.solver_sets.len() as i32);
246
247    let body_id = world.body_id_pool.alloc_id();
248
249    let mut lock_flags = 0u32;
250    lock_flags |= if def.motion_locks.linear_x {
251        body_flags::LOCK_LINEAR_X
252    } else {
253        0
254    };
255    lock_flags |= if def.motion_locks.linear_y {
256        body_flags::LOCK_LINEAR_Y
257    } else {
258        0
259    };
260    lock_flags |= if def.motion_locks.linear_z {
261        body_flags::LOCK_LINEAR_Z
262    } else {
263        0
264    };
265    lock_flags |= if def.motion_locks.angular_x {
266        body_flags::LOCK_ANGULAR_X
267    } else {
268        0
269    };
270    lock_flags |= if def.motion_locks.angular_y {
271        body_flags::LOCK_ANGULAR_Y
272    } else {
273        0
274    };
275    lock_flags |= if def.motion_locks.angular_z {
276        body_flags::LOCK_ANGULAR_Z
277    } else {
278        0
279    };
280
281    let mut body_sim = BodySim {
282        transform: WorldTransform {
283            p: def.position,
284            q: def.rotation,
285        },
286        center: def.position,
287        rotation0: def.rotation,
288        center0: def.position,
289        min_extent: huge(),
290        max_extent: crate::math_functions::VEC3_ZERO,
291        linear_damping: def.linear_damping,
292        angular_damping: def.angular_damping,
293        gravity_scale: def.gravity_scale,
294        body_id,
295        flags: lock_flags,
296        ..Default::default()
297    };
298    body_sim.flags |= if def.is_bullet {
299        body_flags::IS_BULLET
300    } else {
301        0
302    };
303    body_sim.flags |= if def.allow_fast_rotation {
304        body_flags::ALLOW_FAST_ROTATION
305    } else {
306        0
307    };
308    body_sim.flags |= if def.type_ == BodyType::Dynamic {
309        body_flags::DYNAMIC_FLAG
310    } else {
311        0
312    };
313    body_sim.flags |= if def.enable_sleep {
314        body_flags::ENABLE_SLEEP
315    } else {
316        0
317    };
318    body_sim.flags |= if def.enable_contact_recycling {
319        body_flags::BODY_ENABLE_CONTACT_RECYCLING
320    } else {
321        0
322    };
323    let sim_flags = body_sim.flags;
324
325    let local_index = {
326        let set = &mut world.solver_sets[set_id as usize];
327        set.body_sims.push(body_sim);
328        let local_index = set.body_sims.len() as i32 - 1;
329
330        if set_id == AWAKE_SET {
331            let mut body_state = IDENTITY_BODY_STATE;
332            body_state.linear_velocity = def.linear_velocity;
333            body_state.angular_velocity = def.angular_velocity;
334            body_state.flags = sim_flags;
335            set.body_states.push(body_state);
336
337            set.body_sims[local_index as usize].max_angular_velocity =
338                length(def.angular_velocity) + 5.0;
339        }
340        local_index
341    };
342
343    if body_id == world.bodies.len() as i32 {
344        world.bodies.push(Body::default());
345    } else {
346        debug_assert!(world.bodies[body_id as usize].id == NULL_INDEX);
347    }
348
349    let name_id = world.names.add_name(&def.name);
350
351    {
352        let body = &mut world.bodies[body_id as usize];
353        body.user_data = def.user_data;
354        body.set_index = set_id;
355        body.local_index = local_index;
356        body.generation = body.generation.wrapping_add(1);
357        body.head_shape_id = NULL_INDEX;
358        body.shape_count = 0;
359        body.head_chain_id = NULL_INDEX;
360        body.head_contact_key = NULL_INDEX;
361        body.contact_count = 0;
362        body.head_joint_key = NULL_INDEX;
363        body.joint_count = 0;
364        body.island_id = NULL_INDEX;
365        body.island_index = NULL_INDEX;
366        body.body_move_index = NULL_INDEX;
367        body.id = body_id;
368        body.sleep_threshold = def.sleep_threshold;
369        body.sleep_time = 0.0;
370        body.sleep_velocity = 0.0;
371        body.mass = 0.0;
372        body.inertia = crate::math_functions::MAT3_ZERO;
373        body.name_id = name_id;
374        body.type_ = def.type_;
375        body.flags = sim_flags;
376    }
377
378    // dynamic and kinematic bodies that are enabled need an island
379    if set_id >= AWAKE_SET {
380        create_island_for_body(world, set_id, body_id);
381    }
382
383    world.validate_solver_sets();
384
385    let id = make_body_id(world, body_id);
386    world.locked = false;
387
388    crate::recording::capture::rec(world, |rec, wid| {
389        rec.write_create_body(wid, def, id);
390    });
391
392    id
393}
394
395/// Destroy all contacts attached to a body. (static b3DestroyBodyContacts)
396pub(crate) fn destroy_body_contacts(world: &mut World, body_index: i32, wake_bodies: bool) {
397    let mut edge_key = world.bodies[body_index as usize].head_contact_key;
398    while edge_key != NULL_INDEX {
399        let contact_id = edge_key >> 1;
400        let edge_index = edge_key & 1;
401        edge_key = world.contacts[contact_id as usize].edges[edge_index as usize].next_key;
402        crate::contact::destroy_contact(world, contact_id, wake_bodies);
403    }
404
405    world.validate_solver_sets();
406}
407
408/// Destroy a rigid body. (b3DestroyBody)
409pub fn destroy_body(world: &mut World, body_id: BodyId) {
410    debug_assert!(!world.locked);
411    if world.locked {
412        return;
413    }
414
415    crate::recording::capture::rec(world, |_rec, _wid| {
416        _rec.write_destroy_body(body_id);
417    });
418
419    world.locked = true;
420
421    let body_index = get_body_full_id(world, body_id);
422
423    // Wake bodies attached to this body, even if this body is static.
424    let wake_bodies = true;
425
426    // Destroy the attached joints
427    let mut edge_key = world.bodies[body_index as usize].head_joint_key;
428    while edge_key != NULL_INDEX {
429        let joint_id = edge_key >> 1;
430        let edge_index = edge_key & 1;
431        edge_key = world.joints[joint_id as usize].edges[edge_index as usize].next_key;
432
433        // Careful because this modifies the list being traversed
434        crate::joint::destroy_joint_internal(world, joint_id, wake_bodies);
435    }
436
437    destroy_body_contacts(world, body_index, wake_bodies);
438
439    // Destroy the attached shapes and their broad-phase proxies.
440    let mut shape_id = world.bodies[body_index as usize].head_shape_id;
441    while shape_id != NULL_INDEX {
442        let next = world.shapes[shape_id as usize].next_shape_id;
443        crate::shape::lifecycle::destroy_shape_internal(world, shape_id, body_index, true);
444        shape_id = next;
445    }
446
447    remove_body_from_island(world, body_index);
448
449    let (set_index, local_index) = {
450        let body = &world.bodies[body_index as usize];
451        (body.set_index, body.local_index)
452    };
453
454    let set = &mut world.solver_sets[set_index as usize];
455    let moved_index = {
456        let last = set.body_sims.len() as i32 - 1;
457        set.body_sims.swap_remove(local_index as usize);
458        if local_index < last {
459            Some(last)
460        } else {
461            None
462        }
463    };
464
465    if let Some(moved) = moved_index {
466        let moved_sim = &set.body_sims[local_index as usize];
467        let moved_id = moved_sim.body_id;
468        let moved_body = &mut world.bodies[moved_id as usize];
469        debug_assert!(moved_body.local_index == moved);
470        moved_body.local_index = local_index;
471    }
472
473    if set_index == AWAKE_SET {
474        let result = {
475            let last = world.solver_sets[AWAKE_SET as usize].body_states.len() as i32 - 1;
476            world.solver_sets[AWAKE_SET as usize]
477                .body_states
478                .swap_remove(local_index as usize);
479            if local_index < last {
480                Some(last)
481            } else {
482                None
483            }
484        };
485        debug_assert!(result == moved_index);
486        let _ = result;
487    } else if set_index >= FIRST_SLEEPING_SET
488        && world.solver_sets[set_index as usize].body_sims.is_empty()
489    {
490        destroy_solver_set(world, set_index);
491    }
492
493    world.body_id_pool.free_id(body_index);
494
495    let body = &mut world.bodies[body_index as usize];
496    body.set_index = NULL_INDEX;
497    body.local_index = NULL_INDEX;
498    body.id = NULL_INDEX;
499
500    world.validate_solver_sets();
501    world.locked = false;
502}