Skip to main content

box2d_rust/joint/
lifecycle.rs

1// Joint creation from joint.c: the shared b2CreateJoint machinery and the
2// per-type b2Create*Joint constructors.
3//
4// The C b2CreateJoint returns a (b2Joint*, b2JointSim*) 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. The C memsets the sim union to zero; the Rust payload enum is
8// reset with the per-type Default, whose transient fields (indices, frames,
9// softness) are all overwritten by prepare before use.
10//
11// SPDX-FileCopyrightText: 2023 Erin Catto
12// SPDX-License-Identifier: MIT
13
14use super::*;
15use crate::body::get_body_full_id;
16use crate::constants::linear_slop;
17use crate::core::NULL_INDEX;
18use crate::id::JointId;
19use crate::island::link_joint;
20use crate::math_functions::{
21    clamp_float, is_valid_float, is_valid_transform, max_float, max_int, PI,
22};
23use crate::solver_set::{
24    merge_solver_sets, wake_solver_set, AWAKE_SET, DISABLED_SET, FIRST_SLEEPING_SET, STATIC_SET,
25};
26use crate::types::{
27    BodyType, DistanceJointDef, FilterJointDef, JointDef, MotorJointDef, PrismaticJointDef,
28    RevoluteJointDef, WeldJointDef, WheelJointDef,
29};
30use crate::world::World;
31
32/// An empty payload of the given type, standing in for the C memset of the
33/// union plus the type tag assignment.
34fn empty_payload(joint_type: JointType) -> JointPayload {
35    match joint_type {
36        JointType::Distance => JointPayload::Distance(DistanceJoint::default()),
37        JointType::Filter => JointPayload::Filter,
38        JointType::Motor => JointPayload::Motor(MotorJoint::default()),
39        JointType::Prismatic => JointPayload::Prismatic(PrismaticJoint::default()),
40        JointType::Revolute => JointPayload::Revolute(RevoluteJoint::default()),
41        JointType::Weld => JointPayload::Weld(WeldJoint::default()),
42        JointType::Wheel => JointPayload::Wheel(WheelJoint::default()),
43    }
44}
45
46/// (static b2DestroyContactsBetweenBodies)
47pub(crate) fn destroy_contacts_between_bodies(world: &mut World, body_id_a: i32, body_id_b: i32) {
48    // use the smaller of the two contact lists
49    let (mut contact_key, other_body_id) = {
50        let body_a = &world.bodies[body_id_a as usize];
51        let body_b = &world.bodies[body_id_b as usize];
52        if body_a.contact_count < body_b.contact_count {
53            (body_a.head_contact_key, body_b.id)
54        } else {
55            (body_b.head_contact_key, body_a.id)
56        }
57    };
58
59    // no need to wake bodies when a joint removes collision between them
60    let wake_bodies = false;
61
62    // destroy the contacts
63    while contact_key != NULL_INDEX {
64        let contact_id = contact_key >> 1;
65        let edge_index = contact_key & 1;
66
67        contact_key = world.contacts[contact_id as usize].edges[edge_index as usize].next_key;
68
69        let other_edge_index = edge_index ^ 1;
70        if world.contacts[contact_id as usize].edges[other_edge_index as usize].body_id
71            == other_body_id
72        {
73            // Careful, this removes the contact from the current doubly linked
74            // list
75            crate::contact::destroy_contact(world, contact_id, wake_bodies);
76        }
77    }
78
79    world.validate_solver_sets();
80}
81
82/// Shared joint creation. Returns the raw joint index; the per-type
83/// constructors fill the payload through get_joint_sim. (static b2CreateJoint)
84pub(crate) fn create_joint(world: &mut World, def: &JointDef, joint_type: JointType) -> i32 {
85    debug_assert!(is_valid_transform(def.local_frame_a));
86    debug_assert!(is_valid_transform(def.local_frame_b));
87    debug_assert!(def.body_id_a != def.body_id_b);
88
89    let body_id_a = get_body_full_id(world, def.body_id_a);
90    let body_id_b = get_body_full_id(world, def.body_id_b);
91    let max_set_index = max_int(
92        world.bodies[body_id_a as usize].set_index,
93        world.bodies[body_id_b as usize].set_index,
94    );
95
96    // Create joint id and joint
97    let joint_id = world.joint_id_pool.alloc_id();
98    if joint_id == world.joints.len() as i32 {
99        world.joints.push(Joint::default());
100    }
101
102    {
103        let joint = &mut world.joints[joint_id as usize];
104        joint.joint_id = joint_id;
105        joint.user_data = def.user_data;
106        joint.generation = joint.generation.wrapping_add(1);
107        joint.set_index = NULL_INDEX;
108        joint.color_index = NULL_INDEX;
109        joint.local_index = NULL_INDEX;
110        joint.island_id = NULL_INDEX;
111        joint.island_index = NULL_INDEX;
112        joint.draw_scale = def.draw_scale;
113        joint.type_ = joint_type;
114        joint.collide_connected = def.collide_connected;
115    }
116
117    // Doubly linked list on bodyA
118    {
119        let head_joint_key = world.bodies[body_id_a as usize].head_joint_key;
120        {
121            let joint = &mut world.joints[joint_id as usize];
122            joint.edges[0].body_id = body_id_a;
123            joint.edges[0].prev_key = NULL_INDEX;
124            joint.edges[0].next_key = head_joint_key;
125        }
126
127        let key_a = joint_id << 1;
128        if head_joint_key != NULL_INDEX {
129            let head_joint = &mut world.joints[(head_joint_key >> 1) as usize];
130            head_joint.edges[(head_joint_key & 1) as usize].prev_key = key_a;
131        }
132        let body_a = &mut world.bodies[body_id_a as usize];
133        body_a.head_joint_key = key_a;
134        body_a.joint_count += 1;
135    }
136
137    // Doubly linked list on bodyB
138    {
139        let head_joint_key = world.bodies[body_id_b as usize].head_joint_key;
140        {
141            let joint = &mut world.joints[joint_id as usize];
142            joint.edges[1].body_id = body_id_b;
143            joint.edges[1].prev_key = NULL_INDEX;
144            joint.edges[1].next_key = head_joint_key;
145        }
146
147        let key_b = (joint_id << 1) | 1;
148        if head_joint_key != NULL_INDEX {
149            let head_joint = &mut world.joints[(head_joint_key >> 1) as usize];
150            head_joint.edges[(head_joint_key & 1) as usize].prev_key = key_b;
151        }
152        let body_b = &mut world.bodies[body_id_b as usize];
153        body_b.head_joint_key = key_b;
154        body_b.joint_count += 1;
155    }
156
157    let set_a = world.bodies[body_id_a as usize].set_index;
158    let set_b = world.bodies[body_id_b as usize].set_index;
159    let type_a = world.bodies[body_id_a as usize].type_;
160    let type_b = world.bodies[body_id_b as usize].type_;
161
162    if set_a == DISABLED_SET || set_b == DISABLED_SET {
163        // if either body is disabled, create in disabled set
164        let local_index = world.solver_sets[DISABLED_SET as usize].joint_sims.len() as i32;
165        {
166            let joint = &mut world.joints[joint_id as usize];
167            joint.set_index = DISABLED_SET;
168            joint.local_index = local_index;
169        }
170
171        let joint_sim = JointSim {
172            joint_id,
173            body_id_a,
174            body_id_b,
175            payload: empty_payload(joint_type),
176            ..JointSim::default()
177        };
178        world.solver_sets[DISABLED_SET as usize]
179            .joint_sims
180            .push(joint_sim);
181    } else if type_a != BodyType::Dynamic && type_b != BodyType::Dynamic {
182        // joint is not attached to a dynamic body
183        let local_index = world.solver_sets[STATIC_SET as usize].joint_sims.len() as i32;
184        {
185            let joint = &mut world.joints[joint_id as usize];
186            joint.set_index = STATIC_SET;
187            joint.local_index = local_index;
188        }
189
190        let joint_sim = JointSim {
191            joint_id,
192            body_id_a,
193            body_id_b,
194            payload: empty_payload(joint_type),
195            ..JointSim::default()
196        };
197        world.solver_sets[STATIC_SET as usize]
198            .joint_sims
199            .push(joint_sim);
200    } else if set_a == AWAKE_SET || set_b == AWAKE_SET {
201        // if either body is sleeping, wake it
202        if max_set_index >= FIRST_SLEEPING_SET {
203            wake_solver_set(world, max_set_index);
204        }
205
206        world.joints[joint_id as usize].set_index = AWAKE_SET;
207
208        let (color_index, local_index) =
209            crate::constraint_graph::create_joint_in_graph(world, joint_id);
210        {
211            let joint_sim = &mut world.constraint_graph.colors[color_index as usize].joint_sims
212                [local_index as usize];
213            joint_sim.joint_id = joint_id;
214            joint_sim.body_id_a = body_id_a;
215            joint_sim.body_id_b = body_id_b;
216            joint_sim.payload = empty_payload(joint_type);
217        }
218    } else {
219        // joint connected between sleeping and/or static bodies
220        debug_assert!(set_a >= FIRST_SLEEPING_SET || set_b >= FIRST_SLEEPING_SET);
221        debug_assert!(set_a != STATIC_SET || set_b != STATIC_SET);
222
223        // joint should go into the sleeping set (not static set)
224        let set_index = max_set_index;
225
226        let local_index = world.solver_sets[set_index as usize].joint_sims.len() as i32;
227        {
228            let joint = &mut world.joints[joint_id as usize];
229            joint.set_index = set_index;
230            joint.local_index = local_index;
231        }
232
233        // These must be set to accommodate the merge below
234        let joint_sim = JointSim {
235            joint_id,
236            body_id_a,
237            body_id_b,
238            payload: empty_payload(joint_type),
239            ..JointSim::default()
240        };
241        world.solver_sets[set_index as usize]
242            .joint_sims
243            .push(joint_sim);
244
245        if set_a != set_b && set_a >= FIRST_SLEEPING_SET && set_b >= FIRST_SLEEPING_SET {
246            // merge sleeping sets. The C jointSim pointer is orphaned here;
247            // the Rust port re-fetches through get_joint_sim below.
248            merge_solver_sets(world, set_a, set_b);
249            debug_assert!(
250                world.bodies[body_id_a as usize].set_index
251                    == world.bodies[body_id_b as usize].set_index
252            );
253        }
254    }
255
256    debug_assert!(is_valid_float(def.force_threshold) && def.force_threshold >= 0.0);
257    debug_assert!(is_valid_float(def.torque_threshold) && def.torque_threshold >= 0.0);
258
259    {
260        let joint_sim = get_joint_sim(world, joint_id);
261        joint_sim.local_frame_a = def.local_frame_a;
262        joint_sim.local_frame_b = def.local_frame_b;
263        joint_sim.constraint_hertz = def.constraint_hertz;
264        joint_sim.constraint_damping_ratio = def.constraint_damping_ratio;
265        joint_sim.constraint_softness = crate::solver::Softness {
266            bias_rate: 0.0,
267            mass_scale: 1.0,
268            impulse_scale: 0.0,
269        };
270        joint_sim.force_threshold = def.force_threshold;
271        joint_sim.torque_threshold = def.torque_threshold;
272
273        debug_assert!(joint_sim.joint_id == joint_id);
274        debug_assert!(joint_sim.body_id_a == body_id_a);
275        debug_assert!(joint_sim.body_id_b == body_id_b);
276    }
277
278    if world.joints[joint_id as usize].set_index > DISABLED_SET {
279        // Add edge to island graph
280        link_joint(world, joint_id);
281    }
282
283    // If the joint prevents collisions, then destroy all contacts between
284    // attached bodies
285    if !def.collide_connected {
286        destroy_contacts_between_bodies(world, body_id_a, body_id_b);
287    }
288
289    world.validate_solver_sets();
290
291    joint_id
292}
293
294/// Build the public JointId for a raw joint index.
295pub(crate) fn make_joint_id(world: &World, joint_id: i32) -> JointId {
296    JointId {
297        index1: joint_id + 1,
298        world0: world.world_id,
299        generation: world.joints[joint_id as usize].generation,
300    }
301}
302
303/// (b2CreateDistanceJoint)
304pub fn create_distance_joint(world: &mut World, def: &DistanceJointDef) -> JointId {
305    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
306    debug_assert!(is_valid_float(def.length) && def.length > 0.0);
307    debug_assert!(def.lower_spring_force <= def.upper_spring_force);
308
309    let joint_id = create_joint(world, &def.base, JointType::Distance);
310
311    let joint_sim = get_joint_sim(world, joint_id);
312    let joint = joint_sim.distance_mut();
313    *joint = DistanceJoint::default();
314    joint.length = max_float(def.length, linear_slop());
315    joint.hertz = def.hertz;
316    joint.damping_ratio = def.damping_ratio;
317    joint.min_length = max_float(def.min_length, linear_slop());
318    joint.max_length = max_float(def.min_length, def.max_length);
319    joint.max_motor_force = def.max_motor_force;
320    joint.motor_speed = def.motor_speed;
321    joint.enable_spring = def.enable_spring;
322    joint.lower_spring_force = def.lower_spring_force;
323    joint.upper_spring_force = def.upper_spring_force;
324    joint.enable_limit = def.enable_limit;
325    joint.enable_motor = def.enable_motor;
326    joint.impulse = 0.0;
327    joint.lower_impulse = 0.0;
328    joint.upper_impulse = 0.0;
329    joint.motor_impulse = 0.0;
330
331    let id = make_joint_id(world, joint_id);
332
333    // (B2_REC_CREATE)
334    crate::recording::record_op(world, |rec, _| {
335        crate::recording::write_create_joint(
336            rec,
337            crate::recording::OP_CREATE_DISTANCE_JOINT,
338            |buf| crate::recording::rec_w_distancejointdef(buf, def),
339            id,
340        )
341    });
342
343    id
344}
345
346/// (b2CreateMotorJoint)
347pub fn create_motor_joint(world: &mut World, def: &MotorJointDef) -> JointId {
348    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
349
350    let joint_id = create_joint(world, &def.base, JointType::Motor);
351
352    let joint_sim = get_joint_sim(world, joint_id);
353    let joint = joint_sim.motor_mut();
354    *joint = MotorJoint::default();
355    joint.linear_velocity = def.linear_velocity;
356    joint.max_velocity_force = def.max_velocity_force;
357    joint.angular_velocity = def.angular_velocity;
358    joint.max_velocity_torque = def.max_velocity_torque;
359    joint.linear_hertz = def.linear_hertz;
360    joint.linear_damping_ratio = def.linear_damping_ratio;
361    joint.max_spring_force = def.max_spring_force;
362    joint.angular_hertz = def.angular_hertz;
363    joint.angular_damping_ratio = def.angular_damping_ratio;
364    joint.max_spring_torque = def.max_spring_torque;
365
366    let id = make_joint_id(world, joint_id);
367
368    // (B2_REC_CREATE)
369    crate::recording::record_op(world, |rec, _| {
370        crate::recording::write_create_joint(
371            rec,
372            crate::recording::OP_CREATE_MOTOR_JOINT,
373            |buf| crate::recording::rec_w_motorjointdef(buf, def),
374            id,
375        )
376    });
377
378    id
379}
380
381/// (b2CreateFilterJoint)
382pub fn create_filter_joint(world: &mut World, def: &FilterJointDef) -> JointId {
383    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
384
385    let joint_id = create_joint(world, &def.base, JointType::Filter);
386
387    let id = make_joint_id(world, joint_id);
388
389    // (B2_REC_CREATE)
390    crate::recording::record_op(world, |rec, _| {
391        crate::recording::write_create_joint(
392            rec,
393            crate::recording::OP_CREATE_FILTER_JOINT,
394            |buf| crate::recording::rec_w_filterjointdef(buf, def),
395            id,
396        )
397    });
398
399    id
400}
401
402/// (b2CreatePrismaticJoint)
403pub fn create_prismatic_joint(world: &mut World, def: &PrismaticJointDef) -> JointId {
404    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
405    debug_assert!(def.lower_translation <= def.upper_translation);
406
407    let joint_id = create_joint(world, &def.base, JointType::Prismatic);
408
409    let joint_sim = get_joint_sim(world, joint_id);
410    let joint = joint_sim.prismatic_mut();
411    *joint = PrismaticJoint::default();
412    joint.hertz = def.hertz;
413    joint.damping_ratio = def.damping_ratio;
414    joint.target_translation = def.target_translation;
415    joint.lower_translation = def.lower_translation;
416    joint.upper_translation = def.upper_translation;
417    joint.max_motor_force = def.max_motor_force;
418    joint.motor_speed = def.motor_speed;
419    joint.enable_spring = def.enable_spring;
420    joint.enable_limit = def.enable_limit;
421    joint.enable_motor = def.enable_motor;
422
423    let id = make_joint_id(world, joint_id);
424
425    // (B2_REC_CREATE)
426    crate::recording::record_op(world, |rec, _| {
427        crate::recording::write_create_joint(
428            rec,
429            crate::recording::OP_CREATE_PRISMATIC_JOINT,
430            |buf| crate::recording::rec_w_prismaticjointdef(buf, def),
431            id,
432        )
433    });
434
435    id
436}
437
438/// (b2CreateRevoluteJoint)
439pub fn create_revolute_joint(world: &mut World, def: &RevoluteJointDef) -> JointId {
440    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
441    debug_assert!(def.lower_angle <= def.upper_angle);
442    debug_assert!(def.lower_angle >= -0.99 * PI);
443    debug_assert!(def.upper_angle <= 0.99 * PI);
444
445    let joint_id = create_joint(world, &def.base, JointType::Revolute);
446
447    let joint_sim = get_joint_sim(world, joint_id);
448    let joint = joint_sim.revolute_mut();
449    *joint = RevoluteJoint::default();
450    joint.target_angle = clamp_float(def.target_angle, -PI, PI);
451    joint.hertz = def.hertz;
452    joint.damping_ratio = def.damping_ratio;
453    joint.lower_angle = def.lower_angle;
454    joint.upper_angle = def.upper_angle;
455    joint.max_motor_torque = def.max_motor_torque;
456    joint.motor_speed = def.motor_speed;
457    joint.enable_spring = def.enable_spring;
458    joint.enable_limit = def.enable_limit;
459    joint.enable_motor = def.enable_motor;
460
461    let id = make_joint_id(world, joint_id);
462
463    // (B2_REC_CREATE)
464    crate::recording::record_op(world, |rec, _| {
465        crate::recording::write_create_joint(
466            rec,
467            crate::recording::OP_CREATE_REVOLUTE_JOINT,
468            |buf| crate::recording::rec_w_revolutejointdef(buf, def),
469            id,
470        )
471    });
472
473    id
474}
475
476/// (b2CreateWeldJoint)
477pub fn create_weld_joint(world: &mut World, def: &WeldJointDef) -> JointId {
478    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
479
480    let joint_id = create_joint(world, &def.base, JointType::Weld);
481
482    let joint_sim = get_joint_sim(world, joint_id);
483    let joint = joint_sim.weld_mut();
484    *joint = WeldJoint::default();
485    joint.linear_hertz = def.linear_hertz;
486    joint.linear_damping_ratio = def.linear_damping_ratio;
487    joint.angular_hertz = def.angular_hertz;
488    joint.angular_damping_ratio = def.angular_damping_ratio;
489
490    let id = make_joint_id(world, joint_id);
491
492    // (B2_REC_CREATE)
493    crate::recording::record_op(world, |rec, _| {
494        crate::recording::write_create_joint(
495            rec,
496            crate::recording::OP_CREATE_WELD_JOINT,
497            |buf| crate::recording::rec_w_weldjointdef(buf, def),
498            id,
499        )
500    });
501
502    id
503}
504
505/// (b2CreateWheelJoint)
506pub fn create_wheel_joint(world: &mut World, def: &WheelJointDef) -> JointId {
507    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
508    debug_assert!(def.lower_translation <= def.upper_translation);
509
510    let joint_id = create_joint(world, &def.base, JointType::Wheel);
511
512    let joint_sim = get_joint_sim(world, joint_id);
513    let joint = joint_sim.wheel_mut();
514    *joint = WheelJoint::default();
515    joint.lower_translation = def.lower_translation;
516    joint.upper_translation = def.upper_translation;
517    joint.max_motor_torque = def.max_motor_torque;
518    joint.motor_speed = def.motor_speed;
519    joint.hertz = def.hertz;
520    joint.damping_ratio = def.damping_ratio;
521    joint.enable_spring = def.enable_spring;
522    joint.enable_limit = def.enable_limit;
523    joint.enable_motor = def.enable_motor;
524
525    let id = make_joint_id(world, joint_id);
526
527    // (B2_REC_CREATE)
528    crate::recording::record_op(world, |rec, _| {
529        crate::recording::write_create_joint(
530            rec,
531            crate::recording::OP_CREATE_WHEEL_JOINT,
532            |buf| crate::recording::rec_w_wheeljointdef(buf, def),
533            id,
534        )
535    });
536
537    id
538}
539
540/// (b2DestroyJoint)
541pub fn destroy_joint(world: &mut World, joint_id: JointId, wake_attached: bool) {
542    crate::recording::record_op(world, |rec, _| {
543        crate::recording::write_destroy_joint(rec, joint_id, wake_attached)
544    });
545    let joint_index = get_joint_full_id(world, joint_id);
546    destroy_joint_internal(world, joint_index, wake_attached);
547}