Skip to main content

box2d_rust/body/
lifecycle.rs

1// Body creation and mass update from body.c.
2// SPDX-FileCopyrightText: 2023 Erin Catto
3// SPDX-License-Identifier: MIT
4
5use super::{body_flags, create_island_for_body, make_body_id, Body, BodySim, BodyState};
6use crate::constants::huge;
7use crate::core::NULL_INDEX;
8use crate::id::BodyId;
9use crate::math_functions::{
10    is_valid_float, is_valid_position, is_valid_rotation, is_valid_vec2,
11    ROT_IDENTITY as ROT_IDENTITY_,
12};
13use crate::solver_set::{SolverSet, AWAKE_SET, DISABLED_SET, STATIC_SET};
14use crate::types::BodyType;
15use crate::world::World;
16
17/// Create a rigid body given a definition. (b2CreateBody)
18pub fn create_body(world: &mut World, def: &crate::types::BodyDef) -> BodyId {
19    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
20    debug_assert!(is_valid_position(def.position));
21    debug_assert!(is_valid_rotation(def.rotation));
22    debug_assert!(is_valid_vec2(def.linear_velocity));
23    debug_assert!(is_valid_float(def.angular_velocity));
24    debug_assert!(is_valid_float(def.linear_damping) && def.linear_damping >= 0.0);
25    debug_assert!(is_valid_float(def.angular_damping) && def.angular_damping >= 0.0);
26    debug_assert!(is_valid_float(def.sleep_threshold) && def.sleep_threshold >= 0.0);
27    debug_assert!(is_valid_float(def.gravity_scale));
28
29    debug_assert!(!world.locked);
30    if world.locked {
31        return BodyId::default();
32    }
33
34    let is_awake = (def.is_awake || !def.enable_sleep) && def.is_enabled;
35
36    // determine the solver set
37    let set_id;
38    if !def.is_enabled {
39        // any body type can be disabled
40        set_id = DISABLED_SET;
41    } else if def.type_ == BodyType::Static {
42        set_id = STATIC_SET;
43    } else if is_awake {
44        set_id = AWAKE_SET;
45    } else {
46        // new set for a sleeping body in its own island
47        set_id = world.solver_set_id_pool.alloc_id();
48        if set_id == world.solver_sets.len() as i32 {
49            // Create a zero initialized solver set. All sub-arrays are also
50            // zero initialized.
51            world.solver_sets.push(SolverSet::default());
52        } else {
53            debug_assert!(world.solver_sets[set_id as usize].set_index == NULL_INDEX);
54        }
55
56        world.solver_sets[set_id as usize].set_index = set_id;
57    }
58
59    debug_assert!(0 <= set_id && set_id < world.solver_sets.len() as i32);
60
61    let body_id = world.body_id_pool.alloc_id();
62
63    let mut lock_flags = 0u32;
64    lock_flags |= if def.motion_locks.linear_x {
65        body_flags::LOCK_LINEAR_X
66    } else {
67        0
68    };
69    lock_flags |= if def.motion_locks.linear_y {
70        body_flags::LOCK_LINEAR_Y
71    } else {
72        0
73    };
74    lock_flags |= if def.motion_locks.angular_z {
75        body_flags::LOCK_ANGULAR_Z
76    } else {
77        0
78    };
79
80    let set = &mut world.solver_sets[set_id as usize];
81    let mut body_sim = BodySim {
82        transform: crate::math_functions::WorldTransform {
83            p: def.position,
84            q: def.rotation,
85        },
86        center: def.position,
87        rotation0: def.rotation,
88        center0: def.position,
89        min_extent: huge(),
90        max_extent: 0.0,
91        linear_damping: def.linear_damping,
92        angular_damping: def.angular_damping,
93        gravity_scale: def.gravity_scale,
94        body_id,
95        flags: lock_flags,
96        ..Default::default()
97    };
98    body_sim.flags |= if def.is_bullet {
99        body_flags::IS_BULLET
100    } else {
101        0
102    };
103    body_sim.flags |= if def.allow_fast_rotation {
104        body_flags::ALLOW_FAST_ROTATION
105    } else {
106        0
107    };
108    body_sim.flags |= if def.type_ == BodyType::Dynamic {
109        body_flags::DYNAMIC_FLAG
110    } else {
111        0
112    };
113    body_sim.flags |= if def.enable_sleep {
114        body_flags::ENABLE_SLEEP
115    } else {
116        0
117    };
118    body_sim.flags |= if def.enable_contact_recycling {
119        body_flags::BODY_ENABLE_CONTACT_RECYCLING
120    } else {
121        0
122    };
123    let sim_flags = body_sim.flags;
124    set.body_sims.push(body_sim);
125    let local_index = set.body_sims.len() as i32 - 1;
126
127    if set_id == AWAKE_SET {
128        set.body_states.push(BodyState {
129            linear_velocity: def.linear_velocity,
130            angular_velocity: def.angular_velocity,
131            delta_rotation: ROT_IDENTITY_,
132            flags: sim_flags,
133            ..Default::default()
134        });
135    }
136
137    if body_id == world.bodies.len() as i32 {
138        world.bodies.push(Body::default());
139    } else {
140        debug_assert!(world.bodies[body_id as usize].id == NULL_INDEX);
141    }
142
143    let body = &mut world.bodies[body_id as usize];
144
145    // C: strncpy into char[B2_NAME_LENGTH + 1]; the Rust name is truncated to
146    // the same limit.
147    body.name = def
148        .name
149        .chars()
150        .take(crate::constants::NAME_LENGTH as usize)
151        .collect();
152
153    body.user_data = def.user_data;
154    body.set_index = set_id;
155    body.local_index = local_index;
156    body.generation += 1;
157    body.head_shape_id = NULL_INDEX;
158    body.shape_count = 0;
159    body.head_chain_id = NULL_INDEX;
160    body.head_contact_key = NULL_INDEX;
161    body.contact_count = 0;
162    body.head_joint_key = NULL_INDEX;
163    body.joint_count = 0;
164    body.island_id = NULL_INDEX;
165    body.island_index = NULL_INDEX;
166    body.body_move_index = NULL_INDEX;
167    body.id = body_id;
168    body.mass = 0.0;
169    body.inertia = 0.0;
170    body.sleep_threshold = def.sleep_threshold;
171    body.sleep_time = 0.0;
172    body.type_ = def.type_;
173    body.flags = sim_flags;
174
175    // dynamic and kinematic bodies that are enabled need an island
176    if set_id >= AWAKE_SET {
177        create_island_for_body(world, set_id, body_id);
178    }
179
180    world.validate_solver_sets();
181
182    let id = make_body_id(world, body_id);
183
184    // (B2_REC_CREATE(world, CreateBody, id, worldId, *def))
185    crate::recording::record_op(world, |rec, _| {
186        crate::recording::write_create_body(rec, def, id)
187    });
188
189    id
190}
191
192/// Update a body's mass, center of mass, rotational inertia, and extents from
193/// its shapes. (b2UpdateBodyMassData)
194pub fn update_body_mass_data(world: &mut World, body_id: i32) {
195    use crate::collision::MassData;
196    use crate::math_functions::{
197        add, cross_sv, dot, max_float, min_float, mul_add, mul_sv, sub, sub_pos,
198        transform_world_point, VEC2_ZERO,
199    };
200    use crate::shape::{compute_shape_extent, compute_shape_mass};
201
202    let (set_index, local_index, body_type, head_shape_id, shape_count) = {
203        let body = &mut world.bodies[body_id as usize];
204        // Mass is no longer dirty
205        body.flags &= !body_flags::DIRTY_MASS;
206        // Compute mass data from shapes. Each shape has its own density.
207        body.mass = 0.0;
208        body.inertia = 0.0;
209        (
210            body.set_index,
211            body.local_index,
212            body.type_,
213            body.head_shape_id,
214            body.shape_count,
215        )
216    };
217
218    {
219        let sim = &mut world.solver_sets[set_index as usize].body_sims[local_index as usize];
220        sim.inv_mass = 0.0;
221        sim.inv_inertia = 0.0;
222        sim.local_center = VEC2_ZERO;
223        sim.min_extent = huge();
224        sim.max_extent = 0.0;
225    }
226
227    // Static and kinematic sims have zero mass.
228    if body_type != BodyType::Dynamic {
229        {
230            let sim = &mut world.solver_sets[set_index as usize].body_sims[local_index as usize];
231            sim.center = sim.transform.p;
232            sim.center0 = sim.center;
233        }
234
235        // Need extents for kinematic bodies for sleeping to work correctly.
236        if body_type == BodyType::Kinematic {
237            let mut shape_id = head_shape_id;
238            while shape_id != NULL_INDEX {
239                let extent = {
240                    let s = &world.shapes[shape_id as usize];
241                    let e = compute_shape_extent(s, VEC2_ZERO);
242                    shape_id = s.next_shape_id;
243                    e
244                };
245                let sim =
246                    &mut world.solver_sets[set_index as usize].body_sims[local_index as usize];
247                sim.min_extent = min_float(sim.min_extent, extent.min_extent);
248                sim.max_extent = max_float(sim.max_extent, extent.max_extent);
249            }
250        }
251
252        return;
253    }
254
255    // C uses arena scratch (b2StackAlloc); a Vec is the Rust equivalent.
256    let mut masses: Vec<MassData> = Vec::with_capacity(shape_count as usize);
257
258    // Accumulate mass over all shapes.
259    let mut local_center = VEC2_ZERO;
260    let mut shape_id = head_shape_id;
261    while shape_id != NULL_INDEX {
262        let (mass_data, next) = {
263            let s = &world.shapes[shape_id as usize];
264            let next = s.next_shape_id;
265            if s.density == 0.0 {
266                (MassData::default(), next)
267            } else {
268                (compute_shape_mass(s), next)
269            }
270        };
271        shape_id = next;
272
273        if mass_data.mass != 0.0 {
274            world.bodies[body_id as usize].mass += mass_data.mass;
275            local_center = mul_add(local_center, mass_data.mass, mass_data.center);
276        }
277        masses.push(mass_data);
278    }
279
280    // Compute center of mass.
281    let body_mass = world.bodies[body_id as usize].mass;
282    if body_mass > 0.0 {
283        let inv_mass = 1.0 / body_mass;
284        world.solver_sets[set_index as usize].body_sims[local_index as usize].inv_mass = inv_mass;
285        local_center = mul_sv(inv_mass, local_center);
286    }
287
288    // Second loop to accumulate the rotational inertia about the center of mass
289    for mass_data in &masses {
290        if mass_data.mass == 0.0 {
291            continue;
292        }
293
294        // Shift to center of mass. This is safe because it can only increase.
295        let offset = sub(local_center, mass_data.center);
296        let inertia = mass_data.rotational_inertia + mass_data.mass * dot(offset, offset);
297        world.bodies[body_id as usize].inertia += inertia;
298    }
299
300    debug_assert!(world.bodies[body_id as usize].inertia >= 0.0);
301
302    let inertia = world.bodies[body_id as usize].inertia;
303    if inertia > 0.0 {
304        world.solver_sets[set_index as usize].body_sims[local_index as usize].inv_inertia =
305            1.0 / inertia;
306    } else {
307        world.bodies[body_id as usize].inertia = 0.0;
308        world.solver_sets[set_index as usize].body_sims[local_index as usize].inv_inertia = 0.0;
309    }
310
311    // Move center of mass.
312    let old_center = {
313        let sim = &mut world.solver_sets[set_index as usize].body_sims[local_index as usize];
314        let old = sim.center;
315        sim.local_center = local_center;
316        sim.center = transform_world_point(sim.transform, sim.local_center);
317        sim.center0 = sim.center;
318        old
319    };
320
321    // Update center of mass velocity
322    if set_index == AWAKE_SET {
323        let new_center =
324            world.solver_sets[set_index as usize].body_sims[local_index as usize].center;
325        let state = &mut world.solver_sets[AWAKE_SET as usize].body_states[local_index as usize];
326        let delta_linear = cross_sv(state.angular_velocity, sub_pos(new_center, old_center));
327        state.linear_velocity = add(state.linear_velocity, delta_linear);
328    }
329
330    // Compute body extents relative to center of mass
331    let mut shape_id = head_shape_id;
332    while shape_id != NULL_INDEX {
333        let extent = {
334            let s = &world.shapes[shape_id as usize];
335            let e = compute_shape_extent(s, local_center);
336            shape_id = s.next_shape_id;
337            e
338        };
339        let sim = &mut world.solver_sets[set_index as usize].body_sims[local_index as usize];
340        sim.min_extent = min_float(sim.min_extent, extent.min_extent);
341        sim.max_extent = max_float(sim.max_extent, extent.max_extent);
342    }
343}
344
345/// Destroy the attached contacts. (static b2DestroyBodyContacts)
346pub(crate) fn destroy_body_contacts(world: &mut World, body_id: i32, wake_bodies: bool) {
347    let mut edge_key = world.bodies[body_id as usize].head_contact_key;
348    while edge_key != NULL_INDEX {
349        let contact_id = edge_key >> 1;
350        let edge_index = edge_key & 1;
351
352        edge_key = world.contacts[contact_id as usize].edges[edge_index as usize].next_key;
353        crate::contact::destroy_contact(world, contact_id, wake_bodies);
354    }
355
356    world.validate_solver_sets();
357}
358
359/// Destroy a rigid body and everything attached to it: joints, contacts,
360/// shapes, and chains. (b2DestroyBody)
361pub fn destroy_body(world: &mut World, body_id: BodyId) {
362    use super::{remove_body_from_island, remove_body_sim};
363    use crate::body::get_body_full_id;
364    use crate::solver_set::FIRST_SLEEPING_SET;
365
366    crate::recording::record_op(world, |rec, _| {
367        crate::recording::write_body_marker(rec, crate::recording::OP_DESTROY_BODY, body_id)
368    });
369
370    let body_index = get_body_full_id(world, body_id);
371
372    // Wake bodies attached to this body, even if this body is static.
373    let wake_bodies = true;
374
375    // Destroy the attached joints
376    let mut edge_key = world.bodies[body_index as usize].head_joint_key;
377    while edge_key != NULL_INDEX {
378        let joint_id = edge_key >> 1;
379        let edge_index = edge_key & 1;
380
381        edge_key = world.joints[joint_id as usize].edges[edge_index as usize].next_key;
382
383        // Careful because this modifies the list being traversed
384        crate::joint::destroy_joint_internal(world, joint_id, wake_bodies);
385    }
386
387    // Destroy all contacts attached to this body.
388    destroy_body_contacts(world, body_index, wake_bodies);
389
390    // Destroy the attached shapes and their broad-phase proxies.
391    let mut shape_id = world.bodies[body_index as usize].head_shape_id;
392    while shape_id != NULL_INDEX {
393        if world.shapes[shape_id as usize].sensor_index != NULL_INDEX {
394            crate::sensor::destroy_sensor(world, shape_id);
395        }
396
397        {
398            let (shapes, broad_phase) = (&mut world.shapes, &mut world.broad_phase);
399            crate::shape::destroy_shape_proxy(&mut shapes[shape_id as usize], broad_phase);
400        }
401
402        // Return shape to free list.
403        world.shape_id_pool.free_id(shape_id);
404        world.shapes[shape_id as usize].id = NULL_INDEX;
405
406        shape_id = world.shapes[shape_id as usize].next_shape_id;
407    }
408
409    // Destroy the attached chains. The associated shapes have already been
410    // destroyed above.
411    let mut chain_id = world.bodies[body_index as usize].head_chain_id;
412    while chain_id != NULL_INDEX {
413        // Free the chain data. (b2FreeChainData)
414        {
415            let chain = &mut world.chain_shapes[chain_id as usize];
416            chain.shape_indices = Vec::new();
417            chain.materials = Vec::new();
418        }
419
420        // Return chain to free list.
421        world.chain_id_pool.free_id(chain_id);
422        world.chain_shapes[chain_id as usize].id = NULL_INDEX;
423
424        chain_id = world.chain_shapes[chain_id as usize].next_chain_id;
425    }
426
427    remove_body_from_island(world, body_index);
428
429    // Remove body sim from solver set that owns it
430    let (set_index, local_index) = {
431        let body = &world.bodies[body_index as usize];
432        (body.set_index, body.local_index)
433    };
434    remove_body_sim(
435        &mut world.solver_sets[set_index as usize].body_sims,
436        &mut world.bodies,
437        local_index,
438    );
439
440    // Remove body state from awake set
441    if set_index == AWAKE_SET {
442        world.solver_sets[set_index as usize]
443            .body_states
444            .swap_remove(local_index as usize);
445    } else if set_index >= FIRST_SLEEPING_SET
446        && world.solver_sets[set_index as usize].body_sims.is_empty()
447    {
448        // Remove solver set if it is empty
449        crate::solver_set::destroy_solver_set(world, set_index);
450    }
451
452    // Free body and id (preserve body generation)
453    let raw_id = world.bodies[body_index as usize].id;
454    world.body_id_pool.free_id(raw_id);
455
456    let body = &mut world.bodies[body_index as usize];
457    body.set_index = NULL_INDEX;
458    body.local_index = NULL_INDEX;
459    body.id = NULL_INDEX;
460
461    world.validate_solver_sets();
462}