Skip to main content

box2d_rust/body/
mod.rs

1// Port of the body module from box2d-cpp-reference/src/body.h + body.c.
2//
3// Split to satisfy the 800-line file limit:
4// - plumbing.rs  — storage accessors, island glue, sim removal
5// - lifecycle.rs — create_body and update_body_mass_data
6//
7// This file holds the data model. Destruction and the public accessors
8// that need joints/contacts/sensors land in later slices.
9//
10// SPDX-FileCopyrightText: 2023 Erin Catto
11// SPDX-License-Identifier: MIT
12
13use crate::core::NULL_INDEX;
14use crate::distance::Sweep;
15use crate::math_functions::{
16    sub_pos, Pos, Rot, Vec2, WorldTransform, POS_ZERO, ROT_IDENTITY, VEC2_ZERO,
17    WORLD_TRANSFORM_IDENTITY,
18};
19use crate::types::BodyType;
20
21// enum b2BodyFlags
22pub mod body_flags {
23    /// This body has fixed translation along the x-axis
24    pub const LOCK_LINEAR_X: u32 = 0x00000001;
25    /// This body has fixed translation along the y-axis
26    pub const LOCK_LINEAR_Y: u32 = 0x00000002;
27    /// This body has fixed rotation
28    pub const LOCK_ANGULAR_Z: u32 = 0x00000004;
29    /// This flag is used for debug draw
30    pub const IS_FAST: u32 = 0x00000008;
31    /// This dynamic body does a final CCD pass against all body types, but not other bullets
32    pub const IS_BULLET: u32 = 0x00000010;
33    /// This body was speed capped in the current time step
34    pub const IS_SPEED_CAPPED: u32 = 0x00000020;
35    /// This body had a time of impact event in the current time step
36    pub const HAD_TIME_OF_IMPACT: u32 = 0x00000040;
37    /// This body has no limit on angular velocity
38    pub const ALLOW_FAST_ROTATION: u32 = 0x00000080;
39    /// This body needs to have its AABB increased
40    pub const ENLARGE_BOUNDS: u32 = 0x00000100;
41    /// This body is dynamic so the solver should write to it.
42    pub const DYNAMIC_FLAG: u32 = 0x00000200;
43    /// The user deferred mass computation but b2Body_ApplyMassFromShapes was
44    /// not called before the world step.
45    pub const DIRTY_MASS: u32 = 0x00000400;
46    pub const ENABLE_SLEEP: u32 = 0x00000800;
47    pub const BODY_ENABLE_CONTACT_RECYCLING: u32 = 0x00001000;
48
49    /// All lock flags
50    pub const ALL_LOCKS: u32 = LOCK_ANGULAR_Z | LOCK_LINEAR_X | LOCK_LINEAR_Y;
51    /// If this flag is set then the body has fixed rotation
52    pub const FIXED_ROTATION: u32 = LOCK_ANGULAR_Z;
53    /// These flags are transient per time step. These may be different across
54    /// Body, BodySim, and BodyState.
55    pub const BODY_TRANSIENT_FLAGS: u32 = IS_FAST | IS_SPEED_CAPPED | HAD_TIME_OF_IMPACT;
56}
57
58/// Body organizational details that are not used in the solver. (b2Body)
59#[derive(Debug, Clone)]
60pub struct Body {
61    pub user_data: u64,
62
63    /// index of solver set stored in World. May be NULL_INDEX.
64    pub set_index: i32,
65
66    /// body sim and state index within set. May be NULL_INDEX.
67    pub local_index: i32,
68
69    /// [31 : contactId | 1 : edgeIndex]
70    pub head_contact_key: i32,
71    pub contact_count: i32,
72
73    pub head_shape_id: i32,
74    pub shape_count: i32,
75
76    pub head_chain_id: i32,
77
78    /// [31 : jointId | 1 : edgeIndex]
79    pub head_joint_key: i32,
80    pub joint_count: i32,
81
82    /// All enabled dynamic and kinematic bodies are in an island.
83    pub island_id: i32,
84
85    /// Need this island index for faster union-find
86    pub island_index: i32,
87
88    pub mass: f32,
89
90    /// Rotational inertia about the center of mass.
91    pub inertia: f32,
92
93    pub sleep_threshold: f32,
94    pub sleep_time: f32,
95
96    /// this is used to adjust the fellAsleep flag in the body move array
97    pub body_move_index: i32,
98
99    pub id: i32,
100
101    /// body_flags bits
102    pub flags: u32,
103
104    pub type_: BodyType,
105
106    /// Monotonically advanced when a body is allocated in this slot.
107    /// Used to check for invalid BodyId.
108    pub generation: u16,
109
110    /// Body name for debugging (C: char[B2_NAME_LENGTH + 1]).
111    pub name: String,
112}
113
114impl Default for Body {
115    fn default() -> Self {
116        Body {
117            user_data: 0,
118            set_index: NULL_INDEX,
119            local_index: NULL_INDEX,
120            head_contact_key: NULL_INDEX,
121            contact_count: 0,
122            head_shape_id: NULL_INDEX,
123            shape_count: 0,
124            head_chain_id: NULL_INDEX,
125            head_joint_key: NULL_INDEX,
126            joint_count: 0,
127            island_id: NULL_INDEX,
128            island_index: NULL_INDEX,
129            mass: 0.0,
130            inertia: 0.0,
131            sleep_threshold: 0.0,
132            sleep_time: 0.0,
133            body_move_index: NULL_INDEX,
134            id: NULL_INDEX,
135            flags: 0,
136            type_: BodyType::Static,
137            generation: 0,
138            name: String::new(),
139        }
140    }
141}
142
143/// Body state, designed for fast conversion to and from SIMD via
144/// scatter-gather. Only awake dynamic and kinematic bodies have a body state.
145/// Used in the performance critical constraint solver. (b2BodyState, 32 bytes)
146///
147/// The solver operates on the body state. The body state array does not hold
148/// static bodies; their delta rotation is identity via a dummy state.
149#[derive(Debug, Clone, Copy, PartialEq)]
150pub struct BodyState {
151    pub linear_velocity: Vec2,
152    pub angular_velocity: f32,
153    /// body_flags bits. Important flags: locking, dynamic.
154    pub flags: u32,
155    /// Using delta position reduces round-off error far from the origin
156    pub delta_position: Vec2,
157    /// Using delta rotation because the solver cannot access the full rotation
158    /// on static bodies and must use zero delta rotation (c,s) = (1,0)
159    pub delta_rotation: Rot,
160}
161
162/// Identity body state, notice the delta_rotation is {1, 0}.
163/// (b2_identityBodyState)
164pub const IDENTITY_BODY_STATE: BodyState = BodyState {
165    linear_velocity: VEC2_ZERO,
166    angular_velocity: 0.0,
167    flags: 0,
168    delta_position: VEC2_ZERO,
169    delta_rotation: ROT_IDENTITY,
170};
171
172impl Default for BodyState {
173    fn default() -> Self {
174        IDENTITY_BODY_STATE
175    }
176}
177
178/// Body simulation data used for integration of position and velocity.
179/// Transform data used for collision and solver preparation. (b2BodySim)
180#[derive(Debug, Clone, Copy, PartialEq)]
181pub struct BodySim {
182    /// transform for body origin, double translation in large world mode
183    pub transform: WorldTransform,
184
185    /// center of mass position in world space
186    pub center: Pos,
187
188    /// previous rotation and COM for TOI
189    pub rotation0: Rot,
190    pub center0: Pos,
191
192    /// location of center of mass relative to the body origin
193    pub local_center: Vec2,
194
195    pub force: Vec2,
196    pub torque: f32,
197
198    /// inverse mass and inertia
199    pub inv_mass: f32,
200    pub inv_inertia: f32,
201
202    pub min_extent: f32,
203    pub max_extent: f32,
204    pub linear_damping: f32,
205    pub angular_damping: f32,
206    pub gravity_scale: f32,
207
208    /// Index of Body
209    pub body_id: i32,
210
211    /// body_flags bits
212    pub flags: u32,
213}
214
215impl Default for BodySim {
216    fn default() -> Self {
217        BodySim {
218            transform: WORLD_TRANSFORM_IDENTITY,
219            center: POS_ZERO,
220            rotation0: ROT_IDENTITY,
221            center0: POS_ZERO,
222            local_center: VEC2_ZERO,
223            force: VEC2_ZERO,
224            torque: 0.0,
225            inv_mass: 0.0,
226            inv_inertia: 0.0,
227            min_extent: 0.0,
228            max_extent: 0.0,
229            linear_damping: 0.0,
230            angular_damping: 0.0,
231            gravity_scale: 0.0,
232            body_id: NULL_INDEX,
233            flags: 0,
234        }
235    }
236}
237
238/// Build a sweep relative to a base position so continuous collision keeps
239/// float precision far from the origin. The base cancels out of the relative
240/// motion the TOI actually solves. (b2MakeRelativeSweep)
241pub fn make_relative_sweep(body_sim: &BodySim, base: Pos) -> Sweep {
242    Sweep {
243        c1: sub_pos(body_sim.center0, base),
244        c2: sub_pos(body_sim.center, base),
245        q1: body_sim.rotation0,
246        q2: body_sim.transform.q,
247        local_center: body_sim.local_center,
248    }
249}
250
251mod access;
252mod api;
253mod forces;
254mod lifecycle;
255mod plumbing;
256mod state;
257
258pub use access::*;
259pub use api::*;
260pub use forces::*;
261pub use lifecycle::*;
262pub use plumbing::*;
263pub use state::*;
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268    use crate::math_functions::{make_rot, to_pos, Vec2};
269    use crate::solver_set::{AWAKE_SET, DISABLED_SET, STATIC_SET};
270    use crate::types::{default_body_def, default_world_def};
271    use crate::world::World;
272
273    #[test]
274    fn create_static_dynamic_and_sleeping_bodies() {
275        let mut world = World::new(&default_world_def());
276
277        // Static body goes in the static set with no island.
278        let s = create_body(&mut world, &default_body_def());
279        let s_index = get_body_full_id(&world, s);
280        assert_eq!(world.bodies[s_index as usize].set_index, STATIC_SET);
281        assert_eq!(world.bodies[s_index as usize].island_id, NULL_INDEX);
282        assert!(world.solver_sets[STATIC_SET as usize]
283            .body_states
284            .is_empty());
285
286        // Dynamic awake body: awake set, body state, island.
287        let mut def = default_body_def();
288        def.type_ = BodyType::Dynamic;
289        def.position = to_pos(Vec2 { x: 2.0, y: 3.0 });
290        def.rotation = make_rot(0.5);
291        def.linear_velocity = Vec2 { x: 1.0, y: -1.0 };
292        def.name = "crate".into();
293        let d = create_body(&mut world, &def);
294        let d_index = get_body_full_id(&world, d);
295        {
296            let body = &world.bodies[d_index as usize];
297            assert_eq!(body.set_index, AWAKE_SET);
298            assert_eq!(body.type_, BodyType::Dynamic);
299            assert!(body.island_id != NULL_INDEX);
300            assert_eq!(body.name, "crate");
301            assert!(body.flags & body_flags::DYNAMIC_FLAG != 0);
302        }
303        let xf = get_body_transform(&world, d_index);
304        assert_eq!(crate::math_functions::to_vec2(xf.p).x, 2.0);
305        let awake = &world.solver_sets[AWAKE_SET as usize];
306        assert_eq!(awake.body_sims.len(), 1);
307        assert_eq!(awake.body_states.len(), 1);
308        assert_eq!(awake.body_states[0].linear_velocity.x, 1.0);
309        assert_eq!(awake.island_sims.len(), 1);
310
311        // Sleeping dynamic body gets its own new sleeping set + island.
312        let mut sleep_def = default_body_def();
313        sleep_def.type_ = BodyType::Dynamic;
314        sleep_def.is_awake = false;
315        let z = create_body(&mut world, &sleep_def);
316        let z_index = get_body_full_id(&world, z);
317        let z_set = world.bodies[z_index as usize].set_index;
318        assert!(z_set >= crate::solver_set::FIRST_SLEEPING_SET);
319        assert_eq!(world.solver_sets[z_set as usize].body_sims.len(), 1);
320        assert!(world.solver_sets[z_set as usize].body_states.is_empty());
321        assert!(world.bodies[z_index as usize].island_id != NULL_INDEX);
322
323        // Disabled body: disabled set, no island.
324        let mut disabled_def = default_body_def();
325        disabled_def.type_ = BodyType::Dynamic;
326        disabled_def.is_enabled = false;
327        let x = create_body(&mut world, &disabled_def);
328        let x_index = get_body_full_id(&world, x);
329        assert_eq!(world.bodies[x_index as usize].set_index, DISABLED_SET);
330        assert_eq!(world.bodies[x_index as usize].island_id, NULL_INDEX);
331
332        // Ids are 1-based with generations.
333        assert_eq!(s.index1, 1);
334        assert_eq!(d.index1, 2);
335        assert!(d.generation >= 1);
336
337        world.validate_solver_sets();
338    }
339
340    #[test]
341    fn island_lifecycle_via_bodies() {
342        let mut world = World::new(&default_world_def());
343
344        let mut def = default_body_def();
345        def.type_ = BodyType::Dynamic;
346        let a = create_body(&mut world, &def);
347        let b = create_body(&mut world, &def);
348        let a_index = get_body_full_id(&world, a);
349        let b_index = get_body_full_id(&world, b);
350
351        assert_eq!(world.island_id_pool.id_count(), 2);
352
353        // Remove body A from its island; the island becomes empty and dies.
354        let a_island = world.bodies[a_index as usize].island_id;
355        remove_body_from_island(&mut world, a_index);
356        assert_eq!(world.bodies[a_index as usize].island_id, NULL_INDEX);
357        assert_eq!(world.islands[a_island as usize].set_index, NULL_INDEX);
358        assert_eq!(world.island_id_pool.id_count(), 1);
359
360        // B's island is intact.
361        crate::island::validate_island(&world, world.bodies[b_index as usize].island_id);
362
363        // Name truncation to NAME_LENGTH.
364        let mut long_name = default_body_def();
365        long_name.name = "abcdefghijklmnop".into();
366        let c = create_body(&mut world, &long_name);
367        let c_index = get_body_full_id(&world, c);
368        assert_eq!(
369            world.bodies[c_index as usize].name.len(),
370            crate::constants::NAME_LENGTH as usize
371        );
372    }
373
374    // Slice-5 test: destroy_shape removes the contact it participates in;
375    // destroy_body tears down shapes, islands, and solver set slots.
376    #[test]
377    fn destroy_shape_and_body_lifecycle() {
378        use crate::broad_phase::update_broad_phase_pairs;
379        use crate::geometry::make_box;
380        use crate::shape::{create_polygon_shape, destroy_shape};
381        use crate::table::shape_pair_key;
382        use crate::types::default_shape_def;
383
384        let mut world = World::new(&default_world_def());
385
386        let mut def = default_body_def();
387        def.type_ = BodyType::Dynamic;
388        let body_a = create_body(&mut world, &def);
389        let body_b = create_body(&mut world, &def);
390        let a_index = get_body_full_id(&world, body_a);
391        let b_index = get_body_full_id(&world, body_b);
392
393        let box_poly = make_box(0.5, 0.5);
394        let shape_def = default_shape_def();
395        let sa = create_polygon_shape(&mut world, body_a, &shape_def, &box_poly);
396        let sb = create_polygon_shape(&mut world, body_b, &shape_def, &box_poly);
397
398        update_broad_phase_pairs(&mut world);
399        assert_eq!(world.contact_id_pool.id_count(), 1);
400
401        // Destroying shape B destroys the contact and unlinks both bodies.
402        destroy_shape(&mut world, sb, true);
403        assert_eq!(world.contact_id_pool.id_count(), 0);
404        assert_eq!(world.shape_id_pool.id_count(), 1);
405        assert_eq!(world.bodies[a_index as usize].contact_count, 0);
406        assert_eq!(world.bodies[b_index as usize].contact_count, 0);
407        assert_eq!(world.bodies[b_index as usize].shape_count, 0);
408        assert_eq!(world.bodies[b_index as usize].head_shape_id, NULL_INDEX);
409        assert_eq!(world.shapes[(sb.index1 - 1) as usize].id, NULL_INDEX);
410        assert!(!world
411            .broad_phase
412            .pair_set
413            .contains_key(shape_pair_key(sa.index1 - 1, sb.index1 - 1)));
414
415        // Destroying body A destroys its shape and empties its island.
416        let island_a = world.bodies[a_index as usize].island_id;
417        assert!(island_a != NULL_INDEX);
418        destroy_body(&mut world, body_a);
419        assert_eq!(world.body_id_pool.id_count(), 1);
420        assert_eq!(world.shape_id_pool.id_count(), 0);
421        assert_eq!(world.bodies[a_index as usize].id, NULL_INDEX);
422        assert_eq!(world.bodies[a_index as usize].set_index, NULL_INDEX);
423        assert_eq!(world.islands[island_a as usize].island_id, NULL_INDEX);
424        assert_eq!(
425            world.solver_sets[AWAKE_SET as usize].body_sims.len(),
426            1,
427            "only body B remains awake"
428        );
429        // The remaining sim belongs to body B with a fixed-up local index.
430        assert_eq!(
431            world.solver_sets[AWAKE_SET as usize].body_sims[0].body_id,
432            b_index
433        );
434        assert_eq!(world.bodies[b_index as usize].local_index, 0);
435
436        destroy_body(&mut world, body_b);
437        assert_eq!(world.body_id_pool.id_count(), 0);
438        assert!(world.solver_sets[AWAKE_SET as usize].body_sims.is_empty());
439        assert!(world.solver_sets[AWAKE_SET as usize].island_sims.is_empty());
440
441        world.validate_solver_sets();
442
443        // Slot reuse: a new body recycles the freed slot with a bumped
444        // generation.
445        let reborn = create_body(&mut world, &def);
446        assert_eq!(get_body_full_id(&world, reborn), 1);
447        assert!(reborn.generation > body_b.generation);
448    }
449}