Skip to main content

box3d_rust/joint/
lifecycle.rs

1// Joint creation from joint.c: the shared b3CreateJoint machinery, contact
2// filtering helpers, and create_filter_joint (no per-type solve payload).
3//
4// The C b3CreateJoint returns a (b3Joint*, b3JointSim*) pair; the Rust port
5// returns the raw joint index and callers re-fetch the sim through
6// get_joint_sim, which also survives the solver-set merge that orphans the C
7// pointer.
8//
9// Unlike Box2D, Box3D does not destroy contacts on joint create when
10// collideConnected is false — only b3Joint_SetCollideConnected(false) does.
11// New pairs are still blocked by should_bodies_collide.
12//
13// SPDX-FileCopyrightText: 2025 Erin Catto
14// SPDX-License-Identifier: MIT
15
16use super::{
17    get_joint_full_id, get_joint_sim, make_joint_id, Joint, JointSim, JointType, JointUnion,
18};
19use crate::body::get_body_full_id;
20use crate::constants::linear_slop;
21use crate::core::NULL_INDEX;
22use crate::id::JointId;
23use crate::island::link_joint;
24use crate::math_functions::{
25    clamp_float, is_valid_float, is_valid_transform, max_float, max_int, min_float, PI,
26};
27use crate::solver_set::{
28    merge_solver_sets, wake_solver_set, AWAKE_SET, DISABLED_SET, FIRST_SLEEPING_SET, STATIC_SET,
29};
30use crate::types::{
31    BodyType, DistanceJointDef, FilterJointDef, JointDef, MotorJointDef, ParallelJointDef,
32    PrismaticJointDef, RevoluteJointDef, SphericalJointDef, WeldJointDef, WheelJointDef,
33};
34use crate::world::World;
35
36/// (static b3DestroyContactsBetweenBodies)
37pub(crate) fn destroy_contacts_between_bodies(world: &mut World, body_id_a: i32, body_id_b: i32) {
38    // use the smaller of the two contact lists
39    let (mut contact_key, other_body_id) = {
40        let body_a = &world.bodies[body_id_a as usize];
41        let body_b = &world.bodies[body_id_b as usize];
42        if body_a.contact_count < body_b.contact_count {
43            (body_a.head_contact_key, body_b.id)
44        } else {
45            (body_b.head_contact_key, body_a.id)
46        }
47    };
48
49    // no need to wake bodies when a joint removes collision between them
50    let wake_bodies = false;
51
52    // destroy the contacts
53    while contact_key != NULL_INDEX {
54        let contact_id = contact_key >> 1;
55        let edge_index = contact_key & 1;
56
57        contact_key = world.contacts[contact_id as usize].edges[edge_index as usize].next_key;
58
59        let other_edge_index = edge_index ^ 1;
60        if world.contacts[contact_id as usize].edges[other_edge_index as usize].body_id
61            == other_body_id
62        {
63            // Careful, this removes the contact from the current doubly linked
64            // list
65            crate::contact::destroy_contact(world, contact_id, wake_bodies);
66        }
67    }
68
69    world.validate_solver_sets();
70}
71
72/// Shared joint creation. Returns the raw joint index; the per-type
73/// constructors fill the payload through get_joint_sim. (static b3CreateJoint)
74pub(crate) fn create_joint(world: &mut World, def: &JointDef, joint_type: JointType) -> i32 {
75    debug_assert!(is_valid_transform(def.local_frame_a));
76    debug_assert!(is_valid_transform(def.local_frame_b));
77    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
78
79    let body_id_a = get_body_full_id(world, def.body_id_a);
80    let body_id_b = get_body_full_id(world, def.body_id_b);
81    let max_set_index = max_int(
82        world.bodies[body_id_a as usize].set_index,
83        world.bodies[body_id_b as usize].set_index,
84    );
85
86    // Create joint id and joint
87    let joint_id = world.joint_id_pool.alloc_id();
88    if joint_id == world.joints.len() as i32 {
89        world.joints.push(Joint::default());
90    }
91
92    {
93        let joint = &mut world.joints[joint_id as usize];
94        joint.joint_id = joint_id;
95        joint.user_data = def.user_data;
96        joint.generation = joint.generation.wrapping_add(1);
97        joint.set_index = NULL_INDEX;
98        joint.color_index = NULL_INDEX;
99        joint.local_index = NULL_INDEX;
100        joint.island_id = NULL_INDEX;
101        joint.island_index = NULL_INDEX;
102        joint.draw_scale = def.draw_scale;
103        joint.type_ = joint_type;
104        joint.collide_connected = def.collide_connected;
105    }
106
107    // Doubly linked list on bodyA
108    {
109        let head_joint_key = world.bodies[body_id_a as usize].head_joint_key;
110        {
111            let joint = &mut world.joints[joint_id as usize];
112            joint.edges[0].body_id = body_id_a;
113            joint.edges[0].prev_key = NULL_INDEX;
114            joint.edges[0].next_key = head_joint_key;
115        }
116
117        let key_a = joint_id << 1;
118        if head_joint_key != NULL_INDEX {
119            let head_joint = &mut world.joints[(head_joint_key >> 1) as usize];
120            head_joint.edges[(head_joint_key & 1) as usize].prev_key = key_a;
121        }
122        let body_a = &mut world.bodies[body_id_a as usize];
123        body_a.head_joint_key = key_a;
124        body_a.joint_count += 1;
125    }
126
127    // Doubly linked list on bodyB
128    {
129        let head_joint_key = world.bodies[body_id_b as usize].head_joint_key;
130        {
131            let joint = &mut world.joints[joint_id as usize];
132            joint.edges[1].body_id = body_id_b;
133            joint.edges[1].prev_key = NULL_INDEX;
134            joint.edges[1].next_key = head_joint_key;
135        }
136
137        let key_b = (joint_id << 1) | 1;
138        if head_joint_key != NULL_INDEX {
139            let head_joint = &mut world.joints[(head_joint_key >> 1) as usize];
140            head_joint.edges[(head_joint_key & 1) as usize].prev_key = key_b;
141        }
142        let body_b = &mut world.bodies[body_id_b as usize];
143        body_b.head_joint_key = key_b;
144        body_b.joint_count += 1;
145    }
146
147    let set_a = world.bodies[body_id_a as usize].set_index;
148    let set_b = world.bodies[body_id_b as usize].set_index;
149    let type_a = world.bodies[body_id_a as usize].type_;
150    let type_b = world.bodies[body_id_b as usize].type_;
151
152    if set_a == DISABLED_SET || set_b == DISABLED_SET {
153        // if either body is disabled, create in disabled set
154        let local_index = world.solver_sets[DISABLED_SET as usize].joint_sims.len() as i32;
155        {
156            let joint = &mut world.joints[joint_id as usize];
157            joint.set_index = DISABLED_SET;
158            joint.local_index = local_index;
159        }
160
161        let joint_sim = JointSim {
162            joint_id,
163            body_id_a,
164            body_id_b,
165            type_: joint_type,
166            union_: JointUnion::empty(joint_type),
167            ..JointSim::default()
168        };
169        world.solver_sets[DISABLED_SET as usize]
170            .joint_sims
171            .push(joint_sim);
172    } else if type_a != BodyType::Dynamic && type_b != BodyType::Dynamic {
173        // joint is not attached to a dynamic body
174        let local_index = world.solver_sets[STATIC_SET as usize].joint_sims.len() as i32;
175        {
176            let joint = &mut world.joints[joint_id as usize];
177            joint.set_index = STATIC_SET;
178            joint.local_index = local_index;
179        }
180
181        let joint_sim = JointSim {
182            joint_id,
183            body_id_a,
184            body_id_b,
185            type_: joint_type,
186            union_: JointUnion::empty(joint_type),
187            ..JointSim::default()
188        };
189        world.solver_sets[STATIC_SET as usize]
190            .joint_sims
191            .push(joint_sim);
192    } else if set_a == AWAKE_SET || set_b == AWAKE_SET {
193        // if either body is sleeping, wake it
194        if max_set_index >= FIRST_SLEEPING_SET {
195            wake_solver_set(world, max_set_index);
196        }
197
198        world.joints[joint_id as usize].set_index = AWAKE_SET;
199
200        let (color_index, local_index) =
201            crate::constraint_graph::create_joint_in_graph(world, joint_id);
202        {
203            let joint_sim = &mut world.constraint_graph.colors[color_index as usize].joint_sims
204                [local_index as usize];
205            joint_sim.joint_id = joint_id;
206            joint_sim.body_id_a = body_id_a;
207            joint_sim.body_id_b = body_id_b;
208            joint_sim.type_ = joint_type;
209            joint_sim.union_ = JointUnion::empty(joint_type);
210        }
211    } else {
212        // joint connected between sleeping and/or static bodies
213        debug_assert!(set_a >= FIRST_SLEEPING_SET || set_b >= FIRST_SLEEPING_SET);
214        debug_assert!(set_a != STATIC_SET || set_b != STATIC_SET);
215
216        // joint should go into the sleeping set (not static set)
217        let set_index = max_set_index;
218
219        let local_index = world.solver_sets[set_index as usize].joint_sims.len() as i32;
220        {
221            let joint = &mut world.joints[joint_id as usize];
222            joint.set_index = set_index;
223            joint.local_index = local_index;
224        }
225
226        let joint_sim = JointSim {
227            joint_id,
228            body_id_a,
229            body_id_b,
230            type_: joint_type,
231            union_: JointUnion::empty(joint_type),
232            ..JointSim::default()
233        };
234        world.solver_sets[set_index as usize]
235            .joint_sims
236            .push(joint_sim);
237
238        if set_a != set_b && set_a >= FIRST_SLEEPING_SET && set_b >= FIRST_SLEEPING_SET {
239            // merge sleeping sets. The C jointSim pointer is orphaned here;
240            // the Rust port re-fetches through get_joint_sim below.
241            merge_solver_sets(world, set_a, set_b);
242            debug_assert!(
243                world.bodies[body_id_a as usize].set_index
244                    == world.bodies[body_id_b as usize].set_index
245            );
246        }
247    }
248
249    debug_assert!(is_valid_float(def.force_threshold) && def.force_threshold >= 0.0);
250    debug_assert!(is_valid_float(def.torque_threshold) && def.torque_threshold >= 0.0);
251
252    {
253        let joint_sim = get_joint_sim(world, joint_id);
254        joint_sim.local_frame_a = def.local_frame_a;
255        joint_sim.local_frame_b = def.local_frame_b;
256        joint_sim.type_ = joint_type;
257        joint_sim.constraint_hertz = def.constraint_hertz;
258        joint_sim.constraint_damping_ratio = def.constraint_damping_ratio;
259        joint_sim.constraint_softness = crate::solver::Softness {
260            bias_rate: 0.0,
261            mass_scale: 1.0,
262            impulse_scale: 0.0,
263        };
264        joint_sim.force_threshold = def.force_threshold;
265        joint_sim.torque_threshold = def.torque_threshold;
266
267        debug_assert!(joint_sim.joint_id == joint_id);
268        debug_assert!(joint_sim.body_id_a == body_id_a);
269        debug_assert!(joint_sim.body_id_b == body_id_b);
270    }
271
272    if world.joints[joint_id as usize].set_index > DISABLED_SET {
273        // Add edge to island graph
274        link_joint(world, joint_id);
275    }
276
277    // Box3D C does not destroy contacts here (unlike Box2D). Filtering for new
278    // pairs is handled by should_bodies_collide; existing contacts are cleared
279    // only via joint_set_collide_connected(false).
280
281    world.validate_solver_sets();
282
283    joint_id
284}
285
286/// (b3CreateFilterJoint)
287pub fn create_filter_joint(world: &mut World, def: &FilterJointDef) -> JointId {
288    debug_assert!(def.base.internal_value == crate::core::SECRET_COOKIE);
289    debug_assert!(!world.locked);
290    if world.locked {
291        return crate::id::NULL_JOINT_ID;
292    }
293
294    let joint_id = create_joint(world, &def.base, JointType::Filter);
295    let id = make_joint_id(world, joint_id);
296    crate::recording::capture::rec(world, |rec, wid| {
297        rec.write_create_filter_joint(wid, def, id);
298    });
299    id
300}
301
302/// (b3CreateDistanceJoint)
303pub fn create_distance_joint(world: &mut World, def: &DistanceJointDef) -> JointId {
304    debug_assert!(def.base.internal_value == crate::core::SECRET_COOKIE);
305    debug_assert!(is_valid_float(def.length) && def.length > 0.0);
306    debug_assert!(def.lower_spring_force <= def.upper_spring_force);
307    debug_assert!(!world.locked);
308    if world.locked {
309        return crate::id::NULL_JOINT_ID;
310    }
311
312    let joint_id = create_joint(world, &def.base, JointType::Distance);
313
314    let joint_sim = get_joint_sim(world, joint_id);
315    let joint = joint_sim.distance_mut();
316    *joint = super::DistanceJoint::default();
317    joint.length = max_float(def.length, linear_slop());
318    joint.hertz = def.hertz;
319    joint.damping_ratio = def.damping_ratio;
320    joint.lower_spring_force = def.lower_spring_force;
321    joint.upper_spring_force = def.upper_spring_force;
322    joint.min_length = max_float(def.min_length, linear_slop());
323    joint.max_length = max_float(def.min_length, def.max_length);
324    joint.max_motor_force = def.max_motor_force;
325    joint.motor_speed = def.motor_speed;
326    joint.enable_spring = def.enable_spring;
327    joint.enable_limit = def.enable_limit;
328    joint.enable_motor = def.enable_motor;
329    joint.impulse = 0.0;
330    joint.lower_impulse = 0.0;
331    joint.upper_impulse = 0.0;
332    joint.motor_impulse = 0.0;
333
334    let id = make_joint_id(world, joint_id);
335    crate::recording::capture::rec(world, |rec, wid| {
336        rec.write_create_distance_joint(wid, def, id);
337    });
338    id
339}
340
341/// (b3CreateMotorJoint)
342pub fn create_motor_joint(world: &mut World, def: &MotorJointDef) -> JointId {
343    debug_assert!(def.base.internal_value == crate::core::SECRET_COOKIE);
344    debug_assert!(!world.locked);
345    if world.locked {
346        return crate::id::NULL_JOINT_ID;
347    }
348
349    let joint_id = create_joint(world, &def.base, JointType::Motor);
350
351    let joint_sim = get_joint_sim(world, joint_id);
352    let joint = joint_sim.motor_mut();
353    *joint = super::MotorJoint::default();
354    joint.linear_velocity = def.linear_velocity;
355    joint.max_velocity_force = def.max_velocity_force;
356    joint.angular_velocity = def.angular_velocity;
357    joint.max_velocity_torque = def.max_velocity_torque;
358    joint.linear_hertz = def.linear_hertz;
359    joint.linear_damping_ratio = def.linear_damping_ratio;
360    joint.max_spring_force = def.max_spring_force;
361    joint.angular_hertz = def.angular_hertz;
362    joint.angular_damping_ratio = def.angular_damping_ratio;
363    joint.max_spring_torque = def.max_spring_torque;
364
365    let id = make_joint_id(world, joint_id);
366    crate::recording::capture::rec(world, |rec, wid| {
367        rec.write_create_motor_joint(wid, def, id);
368    });
369    id
370}
371
372/// (b3CreateParallelJoint)
373pub fn create_parallel_joint(world: &mut World, def: &ParallelJointDef) -> JointId {
374    debug_assert!(def.base.internal_value == crate::core::SECRET_COOKIE);
375    debug_assert!(is_valid_float(def.hertz) && def.hertz >= 0.0);
376    debug_assert!(is_valid_float(def.damping_ratio) && def.damping_ratio >= 0.0);
377    debug_assert!(is_valid_float(def.max_torque) && def.max_torque >= 0.0);
378    debug_assert!(!world.locked);
379    if world.locked {
380        return crate::id::NULL_JOINT_ID;
381    }
382
383    let joint_id = create_joint(world, &def.base, JointType::Parallel);
384
385    let joint_sim = get_joint_sim(world, joint_id);
386    let joint = joint_sim.parallel_mut();
387    *joint = super::ParallelJoint::default();
388    joint.hertz = def.hertz;
389    joint.damping_ratio = def.damping_ratio;
390    joint.max_torque = def.max_torque;
391
392    let id = make_joint_id(world, joint_id);
393    crate::recording::capture::rec(world, |rec, wid| {
394        rec.write_create_parallel_joint(wid, def, id);
395    });
396    id
397}
398
399/// (b3CreateSphericalJoint)
400pub fn create_spherical_joint(world: &mut World, def: &SphericalJointDef) -> JointId {
401    debug_assert!(def.base.internal_value == crate::core::SECRET_COOKIE);
402    debug_assert!((0.0..=0.99 * PI).contains(&def.cone_angle));
403    debug_assert!(crate::math_functions::is_valid_quat(def.target_rotation));
404    debug_assert!(!world.locked);
405    if world.locked {
406        return crate::id::NULL_JOINT_ID;
407    }
408
409    let joint_id = create_joint(world, &def.base, JointType::Spherical);
410
411    let joint_sim = get_joint_sim(world, joint_id);
412    let joint = joint_sim.spherical_mut();
413    *joint = super::SphericalJoint::default();
414    joint.hertz = def.hertz;
415    joint.damping_ratio = def.damping_ratio;
416    joint.target_rotation = def.target_rotation;
417    joint.cone_angle = clamp_float(def.cone_angle, 0.0, 0.5 * PI);
418
419    let lower_angle = min_float(def.lower_twist_angle, def.upper_twist_angle);
420    let upper_angle = max_float(def.lower_twist_angle, def.upper_twist_angle);
421    joint.lower_twist_angle = clamp_float(lower_angle, -0.99 * PI, 0.99 * PI);
422    joint.upper_twist_angle = clamp_float(upper_angle, -0.99 * PI, 0.99 * PI);
423
424    joint.max_motor_torque = def.max_motor_torque;
425    joint.motor_velocity = def.motor_velocity;
426    joint.enable_spring = def.enable_spring;
427    joint.enable_cone_limit = def.enable_cone_limit;
428    joint.enable_twist_limit = def.enable_twist_limit;
429    joint.enable_motor = def.enable_motor;
430
431    let id = make_joint_id(world, joint_id);
432    crate::recording::capture::rec(world, |rec, wid| {
433        rec.write_create_spherical_joint(wid, def, id);
434    });
435    id
436}
437
438/// (b3CreateWeldJoint)
439pub fn create_weld_joint(world: &mut World, def: &WeldJointDef) -> JointId {
440    debug_assert!(def.base.internal_value == crate::core::SECRET_COOKIE);
441    debug_assert!(def.angular_hertz >= 0.0);
442    debug_assert!(def.angular_damping_ratio >= 0.0);
443    debug_assert!(def.linear_hertz >= 0.0);
444    debug_assert!(def.linear_damping_ratio >= 0.0);
445    debug_assert!(!world.locked);
446    if world.locked {
447        return crate::id::NULL_JOINT_ID;
448    }
449
450    let joint_id = create_joint(world, &def.base, JointType::Weld);
451
452    let joint_sim = get_joint_sim(world, joint_id);
453    let joint = joint_sim.weld_mut();
454    *joint = super::WeldJoint::default();
455    joint.linear_hertz = def.linear_hertz;
456    joint.linear_damping_ratio = def.linear_damping_ratio;
457    joint.angular_hertz = def.angular_hertz;
458    joint.angular_damping_ratio = def.angular_damping_ratio;
459
460    let id = make_joint_id(world, joint_id);
461    crate::recording::capture::rec(world, |rec, wid| {
462        rec.write_create_weld_joint(wid, def, id);
463    });
464    id
465}
466
467/// (b3CreateRevoluteJoint)
468pub fn create_revolute_joint(world: &mut World, def: &RevoluteJointDef) -> JointId {
469    debug_assert!(def.base.internal_value == crate::core::SECRET_COOKIE);
470    debug_assert!(!world.locked);
471    if world.locked {
472        return crate::id::NULL_JOINT_ID;
473    }
474
475    let joint_id = create_joint(world, &def.base, JointType::Revolute);
476
477    let joint_sim = get_joint_sim(world, joint_id);
478    let joint = joint_sim.revolute_mut();
479    *joint = super::RevoluteJoint::default();
480    joint.hertz = def.hertz;
481    joint.damping_ratio = def.damping_ratio;
482    joint.target_angle = clamp_float(def.target_angle, -PI, PI);
483
484    let lower_angle = min_float(def.lower_angle, def.upper_angle);
485    let upper_angle = max_float(def.lower_angle, def.upper_angle);
486    joint.lower_angle = clamp_float(lower_angle, -0.99 * PI, 0.99 * PI);
487    joint.upper_angle = clamp_float(upper_angle, -0.99 * PI, 0.99 * PI);
488
489    joint.max_motor_torque = def.max_motor_torque;
490    joint.motor_speed = def.motor_speed;
491    joint.enable_spring = def.enable_spring;
492    joint.enable_limit = def.enable_limit;
493    joint.enable_motor = def.enable_motor;
494
495    let id = make_joint_id(world, joint_id);
496    crate::recording::capture::rec(world, |rec, wid| {
497        rec.write_create_revolute_joint(wid, def, id);
498    });
499    id
500}
501
502/// (b3CreatePrismaticJoint)
503pub fn create_prismatic_joint(world: &mut World, def: &PrismaticJointDef) -> JointId {
504    debug_assert!(def.base.internal_value == crate::core::SECRET_COOKIE);
505    debug_assert!(def.lower_translation <= def.upper_translation);
506    debug_assert!(!world.locked);
507    if world.locked {
508        return crate::id::NULL_JOINT_ID;
509    }
510
511    let joint_id = create_joint(world, &def.base, JointType::Prismatic);
512
513    let joint_sim = get_joint_sim(world, joint_id);
514    let joint = joint_sim.prismatic_mut();
515    *joint = super::PrismaticJoint::default();
516    joint.hertz = def.hertz;
517    joint.damping_ratio = def.damping_ratio;
518    joint.target_translation = def.target_translation;
519    joint.lower_translation = def.lower_translation;
520    joint.upper_translation = def.upper_translation;
521    joint.max_motor_force = def.max_motor_force;
522    joint.motor_speed = def.motor_speed;
523    joint.enable_spring = def.enable_spring;
524    joint.enable_limit = def.enable_limit;
525    joint.enable_motor = def.enable_motor;
526
527    let id = make_joint_id(world, joint_id);
528    crate::recording::capture::rec(world, |rec, wid| {
529        rec.write_create_prismatic_joint(wid, def, id);
530    });
531    id
532}
533
534/// (b3CreateWheelJoint)
535pub fn create_wheel_joint(world: &mut World, def: &WheelJointDef) -> JointId {
536    debug_assert!(def.base.internal_value == crate::core::SECRET_COOKIE);
537    debug_assert!(def.lower_suspension_limit <= def.upper_suspension_limit);
538    debug_assert!(!world.locked);
539    if world.locked {
540        return crate::id::NULL_JOINT_ID;
541    }
542
543    let joint_id = create_joint(world, &def.base, JointType::Wheel);
544
545    let joint_sim = get_joint_sim(world, joint_id);
546    let joint = joint_sim.wheel_mut();
547    *joint = super::WheelJoint::default();
548    joint.enable_suspension_spring = def.enable_suspension_spring;
549    joint.suspension_hertz = def.suspension_hertz;
550    joint.suspension_damping_ratio = def.suspension_damping_ratio;
551    joint.enable_suspension_limit = def.enable_suspension_limit;
552    joint.lower_suspension_limit = def.lower_suspension_limit;
553    joint.upper_suspension_limit = def.upper_suspension_limit;
554    joint.enable_spin_motor = def.enable_spin_motor;
555    joint.max_spin_torque = def.max_spin_torque;
556    joint.spin_speed = def.spin_speed;
557    joint.enable_steering = def.enable_steering;
558    joint.steering_hertz = def.steering_hertz;
559    joint.steering_damping_ratio = def.steering_damping_ratio;
560    joint.target_steering_angle = def.target_steering_angle;
561    joint.max_steering_torque = def.max_steering_torque;
562    joint.enable_steering_limit = def.enable_steering_limit;
563    joint.lower_steering_limit = def.lower_steering_limit;
564    joint.upper_steering_limit = def.upper_steering_limit;
565
566    let id = make_joint_id(world, joint_id);
567    crate::recording::capture::rec(world, |rec, wid| {
568        rec.write_create_wheel_joint(wid, def, id);
569    });
570    id
571}
572
573/// (b3Joint_SetCollideConnected)
574pub fn joint_set_collide_connected(world: &mut World, joint_id: JointId, should_collide: bool) {
575    crate::recording::with_recording(world, |rec| {
576        rec.write_joint_set_collide_connected(joint_id, should_collide);
577    });
578    debug_assert!(!world.locked);
579    if world.locked {
580        return;
581    }
582
583    let id = get_joint_full_id(world, joint_id);
584    if world.joints[id as usize].collide_connected == should_collide {
585        return;
586    }
587
588    world.joints[id as usize].collide_connected = should_collide;
589
590    let body_id_a = world.joints[id as usize].edges[0].body_id;
591    let body_id_b = world.joints[id as usize].edges[1].body_id;
592
593    if should_collide {
594        // need to tell the broad-phase to look for new pairs for one of the
595        // two bodies. Pick the one with the fewest shapes.
596        let (shape_count_a, shape_count_b) = {
597            let body_a = &world.bodies[body_id_a as usize];
598            let body_b = &world.bodies[body_id_b as usize];
599            (body_a.shape_count, body_b.shape_count)
600        };
601
602        let mut shape_id = if shape_count_a < shape_count_b {
603            world.bodies[body_id_a as usize].head_shape_id
604        } else {
605            world.bodies[body_id_b as usize].head_shape_id
606        };
607
608        while shape_id != NULL_INDEX {
609            let proxy_key = world.shapes[shape_id as usize].proxy_key;
610            let next = world.shapes[shape_id as usize].next_shape_id;
611            if proxy_key != NULL_INDEX {
612                world.broad_phase.buffer_move(proxy_key);
613            }
614            shape_id = next;
615        }
616    } else {
617        destroy_contacts_between_bodies(world, body_id_a, body_id_b);
618    }
619}
620
621/// (b3Joint_GetCollideConnected)
622pub fn joint_get_collide_connected(world: &World, joint_id: JointId) -> bool {
623    let id = get_joint_full_id(world, joint_id);
624    world.joints[id as usize].collide_connected
625}
626
627/// (b3Joint_GetType)
628pub fn joint_get_type(world: &World, joint_id: JointId) -> JointType {
629    let id = get_joint_full_id(world, joint_id);
630    world.joints[id as usize].type_
631}
632
633/// (b3Joint_GetBodyA)
634pub fn joint_get_body_a(world: &World, joint_id: JointId) -> crate::id::BodyId {
635    let id = get_joint_full_id(world, joint_id);
636    let body_index = world.joints[id as usize].edges[0].body_id;
637    crate::body::make_body_id(world, body_index)
638}
639
640/// (b3Joint_GetBodyB)
641pub fn joint_get_body_b(world: &World, joint_id: JointId) -> crate::id::BodyId {
642    let id = get_joint_full_id(world, joint_id);
643    let body_index = world.joints[id as usize].edges[1].body_id;
644    crate::body::make_body_id(world, body_index)
645}
646
647#[cfg(test)]
648#[path = "lifecycle_tests.rs"]
649mod tests;