1use super::{
8 get_distance_joint_force, get_joint_full_id, get_joint_sim, get_joint_sim_ref,
9 get_motor_joint_force, get_motor_joint_torque, get_parallel_joint_torque,
10 get_prismatic_joint_force, get_prismatic_joint_torque, get_revolute_joint_force,
11 get_revolute_joint_torque, get_spherical_joint_force, get_spherical_joint_torque,
12 get_weld_joint_force, get_weld_joint_torque, get_wheel_joint_force, get_wheel_joint_torque,
13 JointSim, JointType,
14};
15use crate::body::{get_body_transform, wake_body};
16use crate::core::NULL_INDEX;
17use crate::id::{JointId, WorldId};
18use crate::math_functions::{
19 abs_float, dot, get_quat_angle, get_swing_angle, get_twist_angle, inv_mul_quat, is_valid_float,
20 is_valid_transform, length, max_float, perp, rotate_vector, sub_pos, transform_world_point,
21 Transform, Vec3, VEC3_AXIS_X, VEC3_ZERO,
22};
23use crate::world::World;
24
25pub fn joint_set_constraint_tuning(
27 world: &mut World,
28 joint_id: JointId,
29 hertz: f32,
30 damping_ratio: f32,
31) {
32 crate::recording::with_recording(world, |rec| {
33 rec.write_joint_set_constraint_tuning(joint_id, hertz, damping_ratio);
34 });
35 debug_assert!(is_valid_float(hertz) && hertz >= 0.0);
36 debug_assert!(is_valid_float(damping_ratio) && damping_ratio >= 0.0);
37
38 let id = get_joint_full_id(world, joint_id);
39 let base = get_joint_sim(world, id);
40 base.constraint_hertz = hertz;
41 base.constraint_damping_ratio = damping_ratio;
42}
43
44pub fn joint_get_constraint_tuning(world: &World, joint_id: JointId) -> (f32, f32) {
46 let id = get_joint_full_id(world, joint_id);
47 let base = get_joint_sim_ref(world, id);
48 (base.constraint_hertz, base.constraint_damping_ratio)
49}
50
51pub fn joint_set_force_threshold(world: &mut World, joint_id: JointId, threshold: f32) {
53 crate::recording::with_recording(world, |rec| {
54 rec.write_joint_set_force_threshold(joint_id, threshold);
55 });
56 debug_assert!(is_valid_float(threshold) && threshold >= 0.0);
57
58 let id = get_joint_full_id(world, joint_id);
59 get_joint_sim(world, id).force_threshold = threshold;
60}
61
62pub fn joint_get_force_threshold(world: &World, joint_id: JointId) -> f32 {
64 let id = get_joint_full_id(world, joint_id);
65 get_joint_sim_ref(world, id).force_threshold
66}
67
68pub fn joint_set_torque_threshold(world: &mut World, joint_id: JointId, threshold: f32) {
70 crate::recording::with_recording(world, |rec| {
71 rec.write_joint_set_torque_threshold(joint_id, threshold);
72 });
73 debug_assert!(is_valid_float(threshold) && threshold >= 0.0);
74
75 let id = get_joint_full_id(world, joint_id);
76 get_joint_sim(world, id).torque_threshold = threshold;
77}
78
79pub fn joint_get_torque_threshold(world: &World, joint_id: JointId) -> f32 {
81 let id = get_joint_full_id(world, joint_id);
82 get_joint_sim_ref(world, id).torque_threshold
83}
84
85pub fn joint_get_world(world: &World, joint_id: JointId) -> WorldId {
87 let _ = get_joint_full_id(world, joint_id);
88 WorldId {
89 index1: joint_id.world0.wrapping_add(1),
90 generation: world.generation,
91 }
92}
93
94pub fn joint_set_local_frame_a(world: &mut World, joint_id: JointId, local_frame: Transform) {
96 crate::recording::with_recording(world, |rec| {
97 rec.write_joint_set_local_frame_a(joint_id, local_frame);
98 });
99 debug_assert!(is_valid_transform(local_frame));
100
101 let id = get_joint_full_id(world, joint_id);
102 get_joint_sim(world, id).local_frame_a = local_frame;
103}
104
105pub fn joint_get_local_frame_a(world: &World, joint_id: JointId) -> Transform {
107 let id = get_joint_full_id(world, joint_id);
108 get_joint_sim_ref(world, id).local_frame_a
109}
110
111pub fn joint_set_local_frame_b(world: &mut World, joint_id: JointId, local_frame: Transform) {
113 crate::recording::with_recording(world, |rec| {
114 rec.write_joint_set_local_frame_b(joint_id, local_frame);
115 });
116 debug_assert!(is_valid_transform(local_frame));
117
118 let id = get_joint_full_id(world, joint_id);
119 get_joint_sim(world, id).local_frame_b = local_frame;
120}
121
122pub fn joint_get_local_frame_b(world: &World, joint_id: JointId) -> Transform {
124 let id = get_joint_full_id(world, joint_id);
125 get_joint_sim_ref(world, id).local_frame_b
126}
127
128pub fn joint_set_user_data(world: &mut World, joint_id: JointId, user_data: u64) {
130 let id = get_joint_full_id(world, joint_id);
131 world.joints[id as usize].user_data = user_data;
132}
133
134pub fn joint_get_user_data(world: &World, joint_id: JointId) -> u64 {
136 let id = get_joint_full_id(world, joint_id);
137 world.joints[id as usize].user_data
138}
139
140pub fn joint_wake_bodies(world: &mut World, joint_id: JointId) {
142 crate::recording::with_recording(world, |rec| {
143 rec.write_joint_wake_bodies(joint_id);
144 });
145 debug_assert!(!world.locked);
146 if world.locked {
147 return;
148 }
149
150 world.locked = true;
151
152 let id = get_joint_full_id(world, joint_id);
153 let body_id_a = world.joints[id as usize].edges[0].body_id;
154 let body_id_b = world.joints[id as usize].edges[1].body_id;
155
156 wake_body(world, body_id_a);
157 wake_body(world, body_id_b);
158
159 world.locked = false;
160}
161
162fn joint_sim_copy(world: &World, joint_id: i32) -> JointSim {
164 *get_joint_sim_ref(world, joint_id)
165}
166
167pub fn joint_get_constraint_force(world: &World, joint_id: JointId) -> Vec3 {
169 let id = get_joint_full_id(world, joint_id);
170 let type_ = world.joints[id as usize].type_;
171 let base = joint_sim_copy(world, id);
172
173 match type_ {
174 JointType::Parallel => VEC3_ZERO,
175 JointType::Distance => get_distance_joint_force(world, &base),
176 JointType::Filter => VEC3_ZERO,
177 JointType::Motor => get_motor_joint_force(world, &base),
178 JointType::Prismatic => get_prismatic_joint_force(world, &base),
179 JointType::Revolute => get_revolute_joint_force(world, &base),
180 JointType::Spherical => get_spherical_joint_force(world, &base),
181 JointType::Weld => get_weld_joint_force(world, &base),
182 JointType::Wheel => get_wheel_joint_force(world, &base),
183 }
184}
185
186pub fn joint_get_constraint_torque(world: &mut World, joint_id: JointId) -> Vec3 {
188 let id = get_joint_full_id(world, joint_id);
189 let type_ = world.joints[id as usize].type_;
190
191 match type_ {
192 JointType::Parallel => {
193 let inv_h = world.inv_h;
194 let mut base = joint_sim_copy(world, id);
195 get_parallel_joint_torque(inv_h, &mut base)
196 }
197 JointType::Distance | JointType::Filter => VEC3_ZERO,
198 JointType::Motor => {
199 let base = joint_sim_copy(world, id);
200 get_motor_joint_torque(world, &base)
201 }
202 JointType::Prismatic => {
203 let base = joint_sim_copy(world, id);
204 get_prismatic_joint_torque(world, &base)
205 }
206 JointType::Revolute => {
207 with_joint_sim_mut(world, id, |world, base| {
209 get_revolute_joint_torque(world, base)
210 })
211 }
212 JointType::Spherical => {
213 let base = joint_sim_copy(world, id);
214 get_spherical_joint_torque(world, &base)
215 }
216 JointType::Weld => {
217 let base = joint_sim_copy(world, id);
218 get_weld_joint_torque(world, &base)
219 }
220 JointType::Wheel => {
221 let base = joint_sim_copy(world, id);
222 get_wheel_joint_torque(world, &base)
223 }
224 }
225}
226
227fn with_joint_sim_mut<R>(
228 world: &mut World,
229 joint_id: i32,
230 f: impl FnOnce(&World, &mut JointSim) -> R,
231) -> R {
232 let set_index = world.joints[joint_id as usize].set_index;
233 let color_index = world.joints[joint_id as usize].color_index;
234 let local_index = world.joints[joint_id as usize].local_index as usize;
235
236 if color_index != NULL_INDEX {
237 let mut sim = std::mem::take(
238 &mut world.constraint_graph.colors[color_index as usize].joint_sims[local_index],
239 );
240 let result = f(world, &mut sim);
241 world.constraint_graph.colors[color_index as usize].joint_sims[local_index] = sim;
242 result
243 } else {
244 let mut sim =
245 std::mem::take(&mut world.solver_sets[set_index as usize].joint_sims[local_index]);
246 let result = f(world, &mut sim);
247 world.solver_sets[set_index as usize].joint_sims[local_index] = sim;
248 result
249 }
250}
251
252pub fn joint_get_linear_separation(world: &World, joint_id: JointId) -> f32 {
254 let id = get_joint_full_id(world, joint_id);
255 let type_ = world.joints[id as usize].type_;
256 let body_id_a = world.joints[id as usize].edges[0].body_id;
257 let body_id_b = world.joints[id as usize].edges[1].body_id;
258 let base = joint_sim_copy(world, id);
259
260 let xf_a = get_body_transform(world, body_id_a);
261 let xf_b = get_body_transform(world, body_id_b);
262
263 let p_a = transform_world_point(xf_a, base.local_frame_a.p);
264 let p_b = transform_world_point(xf_b, base.local_frame_b.p);
265 let dp = sub_pos(p_b, p_a);
266
267 match type_ {
268 JointType::Parallel | JointType::Motor | JointType::Filter => 0.0,
269 JointType::Distance => {
270 let distance_joint = base.distance();
271 let len = length(dp);
272 if distance_joint.enable_spring {
273 if distance_joint.enable_limit {
274 if len < distance_joint.min_length {
275 return distance_joint.min_length - len;
276 }
277 if len > distance_joint.max_length {
278 return len - distance_joint.max_length;
279 }
280 return 0.0;
281 }
282 return 0.0;
283 }
284 abs_float(len - distance_joint.length)
285 }
286 JointType::Prismatic => {
287 let prismatic = base.prismatic();
288 let axis_a = rotate_vector(xf_a.q, VEC3_AXIS_X);
289 let perp_a = perp(axis_a);
290 let perpendicular_separation = abs_float(dot(perp_a, dp));
291 let mut limit_separation = 0.0f32;
292
293 if prismatic.enable_limit {
294 let translation = dot(axis_a, dp);
295 if translation < prismatic.lower_translation {
296 limit_separation = prismatic.lower_translation - translation;
297 }
298 if prismatic.upper_translation < translation {
299 limit_separation = translation - prismatic.upper_translation;
300 }
301 }
302
303 (perpendicular_separation * perpendicular_separation
304 + limit_separation * limit_separation)
305 .sqrt()
306 }
307 JointType::Revolute | JointType::Spherical => length(dp),
308 JointType::Weld => {
309 if base.weld().linear_hertz == 0.0 {
310 length(dp)
311 } else {
312 0.0
313 }
314 }
315 JointType::Wheel => {
316 let wheel = base.wheel();
317 let axis_a = rotate_vector(xf_a.q, VEC3_AXIS_X);
318 let perp_a = perp(axis_a);
319 let perpendicular_separation = abs_float(dot(perp_a, dp));
320 let mut limit_separation = 0.0f32;
321
322 if wheel.enable_suspension_limit {
323 let translation = dot(axis_a, dp);
324 if translation < wheel.lower_suspension_limit {
325 limit_separation = wheel.lower_suspension_limit - translation;
326 }
327 if wheel.upper_suspension_limit < translation {
328 limit_separation = translation - wheel.upper_suspension_limit;
329 }
330 }
331
332 (perpendicular_separation * perpendicular_separation
333 + limit_separation * limit_separation)
334 .sqrt()
335 }
336 }
337}
338
339pub fn joint_get_angular_separation(world: &World, joint_id: JointId) -> f32 {
341 let id = get_joint_full_id(world, joint_id);
342 let type_ = world.joints[id as usize].type_;
343 let body_id_a = world.joints[id as usize].edges[0].body_id;
344 let body_id_b = world.joints[id as usize].edges[1].body_id;
345 let base = joint_sim_copy(world, id);
346
347 let xf_a = get_body_transform(world, body_id_a);
348 let xf_b = get_body_transform(world, body_id_b);
349
350 let mut rel_q = inv_mul_quat(xf_a.q, xf_b.q);
351
352 match type_ {
353 JointType::Parallel => {
354 rel_q.v.z = 0.0;
356 get_quat_angle(rel_q)
357 }
358 JointType::Distance | JointType::Motor | JointType::Filter => 0.0,
359 JointType::Prismatic => get_quat_angle(rel_q),
360 JointType::Revolute => {
361 let revolute = base.revolute();
362 if revolute.enable_limit {
363 let angle = get_twist_angle(rel_q);
364 if angle < revolute.lower_angle || revolute.upper_angle < angle {
365 return get_quat_angle(rel_q);
366 }
367 }
368 rel_q.v.z = 0.0;
370 get_quat_angle(rel_q)
371 }
372 JointType::Spherical => {
373 let spherical = base.spherical();
374 let mut sum = 0.0f32;
375 if spherical.enable_cone_limit {
376 let swing_angle = get_swing_angle(rel_q);
377 sum += max_float(0.0, swing_angle - spherical.cone_angle);
378 }
379 if spherical.enable_twist_limit {
380 let twist_angle = get_twist_angle(rel_q);
381 sum += max_float(0.0, spherical.lower_twist_angle - twist_angle);
382 sum += max_float(0.0, twist_angle - spherical.upper_twist_angle);
383 }
384 sum
385 }
386 JointType::Weld => {
387 if base.weld().angular_hertz == 0.0 {
388 get_quat_angle(rel_q)
389 } else {
390 0.0
391 }
392 }
393 JointType::Wheel => {
394 debug_assert!(false, "wheel angular separation unimplemented in C");
396 0.0
397 }
398 }
399}