Skip to main content

box2d_rust/joint/
api.rs

1// Joint public API from joint.c (b2Joint_*). The C resolves the world from
2// the id via the global registry; the Rust port takes `world` explicitly.
3// b2Joint_GetWorld is not ported (there is no world registry).
4// B2_REC recording is not ported. b2DrawJoint lands with the debug-draw phase.
5//
6// SPDX-FileCopyrightText: 2023 Erin Catto
7// SPDX-License-Identifier: MIT
8//
9// bring-up: exercised by the world API slice and tests.
10#![allow(dead_code)]
11
12use super::*;
13use crate::body::{make_body_id, wake_body};
14use crate::core::NULL_INDEX;
15use crate::id::{BodyId, JointId};
16use crate::math_functions::{
17    abs_float, dot, is_valid_float, is_valid_transform, left_perp, length, relative_angle,
18    rotate_vector, sub, to_relative_transform, transform_point, Transform, Vec2,
19};
20use crate::world::World;
21
22/// (b2Joint_GetType)
23pub fn joint_get_type(world: &World, joint_id: JointId) -> JointType {
24    let joint_index = get_joint_full_id(world, joint_id);
25    world.joints[joint_index as usize].type_
26}
27
28/// (b2Joint_GetBodyA)
29pub fn joint_get_body_a(world: &World, joint_id: JointId) -> BodyId {
30    let joint_index = get_joint_full_id(world, joint_id);
31    make_body_id(world, world.joints[joint_index as usize].edges[0].body_id)
32}
33
34/// (b2Joint_GetBodyB)
35pub fn joint_get_body_b(world: &World, joint_id: JointId) -> BodyId {
36    let joint_index = get_joint_full_id(world, joint_id);
37    make_body_id(world, world.joints[joint_index as usize].edges[1].body_id)
38}
39
40/// (b2Joint_SetLocalFrameA)
41pub fn joint_set_local_frame_a(world: &mut World, joint_id: JointId, local_frame: Transform) {
42    debug_assert!(is_valid_transform(local_frame));
43
44    let joint_index = get_joint_full_id(world, joint_id);
45    let joint_sim = get_joint_sim(world, joint_index);
46    joint_sim.local_frame_a = local_frame;
47}
48
49/// (b2Joint_GetLocalFrameA)
50pub fn joint_get_local_frame_a(world: &World, joint_id: JointId) -> Transform {
51    let joint_index = get_joint_full_id(world, joint_id);
52    get_joint_sim_ref(world, joint_index).local_frame_a
53}
54
55/// (b2Joint_SetLocalFrameB)
56pub fn joint_set_local_frame_b(world: &mut World, joint_id: JointId, local_frame: Transform) {
57    debug_assert!(is_valid_transform(local_frame));
58
59    let joint_index = get_joint_full_id(world, joint_id);
60    let joint_sim = get_joint_sim(world, joint_index);
61    joint_sim.local_frame_b = local_frame;
62}
63
64/// (b2Joint_GetLocalFrameB)
65pub fn joint_get_local_frame_b(world: &World, joint_id: JointId) -> Transform {
66    let joint_index = get_joint_full_id(world, joint_id);
67    get_joint_sim_ref(world, joint_index).local_frame_b
68}
69
70/// (b2Joint_SetCollideConnected)
71pub fn joint_set_collide_connected(world: &mut World, joint_id: JointId, should_collide: bool) {
72    let joint_index = get_joint_full_id(world, joint_id);
73    if world.joints[joint_index as usize].collide_connected == should_collide {
74        return;
75    }
76
77    world.joints[joint_index as usize].collide_connected = should_collide;
78
79    let body_id_a = world.joints[joint_index as usize].edges[0].body_id;
80    let body_id_b = world.joints[joint_index as usize].edges[1].body_id;
81
82    if should_collide {
83        // need to tell the broad-phase to look for new pairs for one of the
84        // two bodies. Pick the one with the fewest shapes.
85        let shape_count_a = world.bodies[body_id_a as usize].shape_count;
86        let shape_count_b = world.bodies[body_id_b as usize].shape_count;
87
88        let mut shape_id = if shape_count_a < shape_count_b {
89            world.bodies[body_id_a as usize].head_shape_id
90        } else {
91            world.bodies[body_id_b as usize].head_shape_id
92        };
93        while shape_id != NULL_INDEX {
94            let proxy_key = world.shapes[shape_id as usize].proxy_key;
95            if proxy_key != NULL_INDEX {
96                world.broad_phase.buffer_move(proxy_key);
97            }
98
99            shape_id = world.shapes[shape_id as usize].next_shape_id;
100        }
101    } else {
102        destroy_contacts_between_bodies(world, body_id_a, body_id_b);
103    }
104}
105
106/// (b2Joint_GetCollideConnected)
107pub fn joint_get_collide_connected(world: &World, joint_id: JointId) -> bool {
108    let joint_index = get_joint_full_id(world, joint_id);
109    world.joints[joint_index as usize].collide_connected
110}
111
112/// (b2Joint_SetUserData)
113pub fn joint_set_user_data(world: &mut World, joint_id: JointId, user_data: u64) {
114    let joint_index = get_joint_full_id(world, joint_id);
115    world.joints[joint_index as usize].user_data = user_data;
116}
117
118/// (b2Joint_GetUserData)
119pub fn joint_get_user_data(world: &World, joint_id: JointId) -> u64 {
120    let joint_index = get_joint_full_id(world, joint_id);
121    world.joints[joint_index as usize].user_data
122}
123
124/// (b2Joint_WakeBodies)
125pub fn joint_wake_bodies(world: &mut World, joint_id: JointId) {
126    let joint_index = get_joint_full_id(world, joint_id);
127    let body_id_a = world.joints[joint_index as usize].edges[0].body_id;
128    let body_id_b = world.joints[joint_index as usize].edges[1].body_id;
129
130    wake_body(world, body_id_a);
131    wake_body(world, body_id_b);
132}
133
134/// (b2Joint_GetConstraintForce)
135pub fn joint_get_constraint_force(world: &World, joint_id: JointId) -> Vec2 {
136    let joint_index = get_joint_full_id(world, joint_id);
137    get_joint_constraint_force(world, joint_index)
138}
139
140/// (b2Joint_GetConstraintTorque)
141pub fn joint_get_constraint_torque(world: &World, joint_id: JointId) -> f32 {
142    let joint_index = get_joint_full_id(world, joint_id);
143    get_joint_constraint_torque(world, joint_index)
144}
145
146/// (b2Joint_GetLinearSeparation)
147pub fn joint_get_linear_separation(world: &World, joint_id: JointId) -> f32 {
148    let joint_index = get_joint_full_id(world, joint_id);
149    let base = get_joint_sim_ref(world, joint_index);
150    let joint = &world.joints[joint_index as usize];
151
152    // Relative to body A so the difference stays in float precision far from
153    // the origin
154    let wxf_a = crate::body::get_body_transform(world, joint.edges[0].body_id);
155    let xf_a = to_relative_transform(wxf_a, wxf_a.p);
156    let xf_b = to_relative_transform(
157        crate::body::get_body_transform(world, joint.edges[1].body_id),
158        wxf_a.p,
159    );
160
161    let p_a = transform_point(xf_a, base.local_frame_a.p);
162    let p_b = transform_point(xf_b, base.local_frame_b.p);
163    let dp = sub(p_b, p_a);
164
165    match &base.payload {
166        JointPayload::Distance(distance_joint) => {
167            let length_ = length(dp);
168            if distance_joint.enable_spring {
169                if distance_joint.enable_limit {
170                    if length_ < distance_joint.min_length {
171                        return distance_joint.min_length - length_;
172                    }
173
174                    if length_ > distance_joint.max_length {
175                        return length_ - distance_joint.max_length;
176                    }
177
178                    return 0.0;
179                }
180
181                return 0.0;
182            }
183
184            abs_float(length_ - distance_joint.length)
185        }
186
187        JointPayload::Motor(_) => 0.0,
188
189        JointPayload::Filter => 0.0,
190
191        JointPayload::Prismatic(prismatic_joint) => {
192            let axis_a = rotate_vector(xf_a.q, Vec2 { x: 1.0, y: 0.0 });
193            let perp_a = left_perp(axis_a);
194            let perpendicular_separation = abs_float(dot(perp_a, dp));
195            let mut limit_separation = 0.0;
196
197            if prismatic_joint.enable_limit {
198                let translation = dot(axis_a, dp);
199                if translation < prismatic_joint.lower_translation {
200                    limit_separation = prismatic_joint.lower_translation - translation;
201                }
202
203                if prismatic_joint.upper_translation < translation {
204                    limit_separation = translation - prismatic_joint.upper_translation;
205                }
206            }
207
208            (perpendicular_separation * perpendicular_separation
209                + limit_separation * limit_separation)
210                .sqrt()
211        }
212
213        JointPayload::Revolute(_) => length(dp),
214
215        JointPayload::Weld(weld_joint) => {
216            if weld_joint.linear_hertz == 0.0 {
217                return length(dp);
218            }
219
220            0.0
221        }
222
223        JointPayload::Wheel(wheel_joint) => {
224            let axis_a = rotate_vector(xf_a.q, Vec2 { x: 1.0, y: 0.0 });
225            let perp_a = left_perp(axis_a);
226            let perpendicular_separation = abs_float(dot(perp_a, dp));
227            let mut limit_separation = 0.0;
228
229            if wheel_joint.enable_limit {
230                let translation = dot(axis_a, dp);
231                if translation < wheel_joint.lower_translation {
232                    limit_separation = wheel_joint.lower_translation - translation;
233                }
234
235                if wheel_joint.upper_translation < translation {
236                    limit_separation = translation - wheel_joint.upper_translation;
237                }
238            }
239
240            (perpendicular_separation * perpendicular_separation
241                + limit_separation * limit_separation)
242                .sqrt()
243        }
244    }
245}
246
247/// (b2Joint_GetAngularSeparation)
248pub fn joint_get_angular_separation(world: &World, joint_id: JointId) -> f32 {
249    let joint_index = get_joint_full_id(world, joint_id);
250    let base = get_joint_sim_ref(world, joint_index);
251    let joint = &world.joints[joint_index as usize];
252
253    let q_a = crate::body::get_body_transform(world, joint.edges[0].body_id).q;
254    let q_b = crate::body::get_body_transform(world, joint.edges[1].body_id).q;
255    let relative_angle_ = relative_angle(q_a, q_b);
256
257    match &base.payload {
258        JointPayload::Distance(_) => 0.0,
259
260        JointPayload::Motor(_) => 0.0,
261
262        JointPayload::Filter => 0.0,
263
264        JointPayload::Prismatic(_) => relative_angle_,
265
266        JointPayload::Revolute(revolute_joint) => {
267            if revolute_joint.enable_limit {
268                let angle = relative_angle_;
269                if angle < revolute_joint.lower_angle {
270                    return revolute_joint.lower_angle - angle;
271                }
272
273                if revolute_joint.upper_angle < angle {
274                    return angle - revolute_joint.upper_angle;
275                }
276            }
277
278            0.0
279        }
280
281        JointPayload::Weld(weld_joint) => {
282            if weld_joint.angular_hertz == 0.0 {
283                return relative_angle_;
284            }
285
286            0.0
287        }
288
289        JointPayload::Wheel(_) => 0.0,
290    }
291}
292
293/// (b2Joint_SetConstraintTuning)
294pub fn joint_set_constraint_tuning(
295    world: &mut World,
296    joint_id: JointId,
297    hertz: f32,
298    damping_ratio: f32,
299) {
300    debug_assert!(is_valid_float(hertz) && hertz >= 0.0);
301    debug_assert!(is_valid_float(damping_ratio) && damping_ratio >= 0.0);
302
303    let joint_index = get_joint_full_id(world, joint_id);
304    let base = get_joint_sim(world, joint_index);
305    base.constraint_hertz = hertz;
306    base.constraint_damping_ratio = damping_ratio;
307}
308
309/// (b2Joint_GetConstraintTuning — C returns through out-pointers)
310pub fn joint_get_constraint_tuning(world: &World, joint_id: JointId) -> (f32, f32) {
311    let joint_index = get_joint_full_id(world, joint_id);
312    let base = get_joint_sim_ref(world, joint_index);
313    (base.constraint_hertz, base.constraint_damping_ratio)
314}
315
316/// (b2Joint_SetForceThreshold)
317pub fn joint_set_force_threshold(world: &mut World, joint_id: JointId, threshold: f32) {
318    debug_assert!(is_valid_float(threshold) && threshold >= 0.0);
319
320    let joint_index = get_joint_full_id(world, joint_id);
321    let base = get_joint_sim(world, joint_index);
322    base.force_threshold = threshold;
323}
324
325/// (b2Joint_GetForceThreshold)
326pub fn joint_get_force_threshold(world: &World, joint_id: JointId) -> f32 {
327    let joint_index = get_joint_full_id(world, joint_id);
328    get_joint_sim_ref(world, joint_index).force_threshold
329}
330
331/// (b2Joint_SetTorqueThreshold)
332pub fn joint_set_torque_threshold(world: &mut World, joint_id: JointId, threshold: f32) {
333    debug_assert!(is_valid_float(threshold) && threshold >= 0.0);
334
335    let joint_index = get_joint_full_id(world, joint_id);
336    let base = get_joint_sim(world, joint_index);
337    base.torque_threshold = threshold;
338}
339
340/// (b2Joint_GetTorqueThreshold)
341pub fn joint_get_torque_threshold(world: &World, joint_id: JointId) -> f32 {
342    let joint_index = get_joint_full_id(world, joint_id);
343    get_joint_sim_ref(world, joint_index).torque_threshold
344}