1use crate::body::{body_flags, get_body_transform, BodyState, IDENTITY_BODY_STATE};
14use crate::core::NULL_INDEX;
15use crate::id::JointId;
16use crate::joint::{get_joint_sim_check_type, get_joint_sim_check_type_ref, JointSim, JointType};
17use crate::math_functions::WorldTransform;
18use crate::math_functions::{
19 add, clamp_float, cross, cross_sv, inv_mul_rot, max_float, min_float, mul_add, mul_rot,
20 mul_sub, mul_sv, relative_angle, rot_get_angle, rotate_vector, solve_22, sub, sub_pos,
21 unwind_angle, Vec2, MAT22_ZERO, PI, VEC2_ZERO,
22};
23use crate::solver::{make_soft, StepContext};
24use crate::solver_set::AWAKE_SET;
25use crate::world::World;
26
27pub fn revolute_joint_enable_spring(world: &mut World, joint_id: JointId, enable_spring: bool) {
43 crate::recording::record_op(world, |rec, _| {
44 crate::recording::write_joint_bool(
45 rec,
46 crate::recording::OP_REVOLUTE_ENABLE_SPRING,
47 joint_id,
48 enable_spring,
49 )
50 });
51 let joint = get_joint_sim_check_type(world, joint_id, JointType::Revolute);
52 let revolute = joint.revolute_mut();
53 if enable_spring != revolute.enable_spring {
54 revolute.enable_spring = enable_spring;
55 revolute.spring_impulse = 0.0;
56 }
57}
58
59pub fn revolute_joint_is_spring_enabled(world: &World, joint_id: JointId) -> bool {
61 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
62 joint.revolute().enable_spring
63}
64
65pub fn revolute_joint_set_spring_hertz(world: &mut World, joint_id: JointId, hertz: f32) {
67 crate::recording::record_op(world, |rec, _| {
68 crate::recording::write_joint_f32(
69 rec,
70 crate::recording::OP_REVOLUTE_SET_SPRING_HERTZ,
71 joint_id,
72 hertz,
73 )
74 });
75 let joint = get_joint_sim_check_type(world, joint_id, JointType::Revolute);
76 joint.revolute_mut().hertz = hertz;
77}
78
79pub fn revolute_joint_get_spring_hertz(world: &World, joint_id: JointId) -> f32 {
81 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
82 joint.revolute().hertz
83}
84
85pub fn revolute_joint_set_spring_damping_ratio(
87 world: &mut World,
88 joint_id: JointId,
89 damping_ratio: f32,
90) {
91 crate::recording::record_op(world, |rec, _| {
92 crate::recording::write_joint_f32(
93 rec,
94 crate::recording::OP_REVOLUTE_SET_SPRING_DAMPING_RATIO,
95 joint_id,
96 damping_ratio,
97 )
98 });
99 let joint = get_joint_sim_check_type(world, joint_id, JointType::Revolute);
100 joint.revolute_mut().damping_ratio = damping_ratio;
101}
102
103pub fn revolute_joint_get_spring_damping_ratio(world: &World, joint_id: JointId) -> f32 {
105 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
106 joint.revolute().damping_ratio
107}
108
109pub fn revolute_joint_set_target_angle(world: &mut World, joint_id: JointId, angle: f32) {
111 crate::recording::record_op(world, |rec, _| {
112 crate::recording::write_joint_f32(
113 rec,
114 crate::recording::OP_REVOLUTE_SET_TARGET_ANGLE,
115 joint_id,
116 angle,
117 )
118 });
119 let joint = get_joint_sim_check_type(world, joint_id, JointType::Revolute);
120 joint.revolute_mut().target_angle = angle;
121}
122
123pub fn revolute_joint_get_target_angle(world: &World, joint_id: JointId) -> f32 {
125 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
126 joint.revolute().target_angle
127}
128
129pub fn revolute_joint_get_angle(world: &World, joint_id: JointId) -> f32 {
131 let joint_sim = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
132 let q_a = mul_rot(
133 get_body_transform(world, joint_sim.body_id_a).q,
134 joint_sim.local_frame_a.q,
135 );
136 let q_b = mul_rot(
137 get_body_transform(world, joint_sim.body_id_b).q,
138 joint_sim.local_frame_b.q,
139 );
140
141 relative_angle(q_a, q_b)
142}
143
144pub fn revolute_joint_enable_limit(world: &mut World, joint_id: JointId, enable_limit: bool) {
146 crate::recording::record_op(world, |rec, _| {
147 crate::recording::write_joint_bool(
148 rec,
149 crate::recording::OP_REVOLUTE_ENABLE_LIMIT,
150 joint_id,
151 enable_limit,
152 )
153 });
154 let joint = get_joint_sim_check_type(world, joint_id, JointType::Revolute);
155 let revolute = joint.revolute_mut();
156 if enable_limit != revolute.enable_limit {
157 revolute.enable_limit = enable_limit;
158 revolute.lower_impulse = 0.0;
159 revolute.upper_impulse = 0.0;
160 }
161}
162
163pub fn revolute_joint_is_limit_enabled(world: &World, joint_id: JointId) -> bool {
165 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
166 joint.revolute().enable_limit
167}
168
169pub fn revolute_joint_get_lower_limit(world: &World, joint_id: JointId) -> f32 {
171 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
172 joint.revolute().lower_angle
173}
174
175pub fn revolute_joint_get_upper_limit(world: &World, joint_id: JointId) -> f32 {
177 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
178 joint.revolute().upper_angle
179}
180
181pub fn revolute_joint_set_limits(world: &mut World, joint_id: JointId, lower: f32, upper: f32) {
183 crate::recording::record_op(world, |rec, _| {
184 crate::recording::write_joint_f32_pair(
185 rec,
186 crate::recording::OP_REVOLUTE_SET_LIMITS,
187 joint_id,
188 lower,
189 upper,
190 )
191 });
192 debug_assert!(lower <= upper);
193 debug_assert!(lower >= -0.99 * PI);
194 debug_assert!(upper <= 0.99 * PI);
195
196 let joint = get_joint_sim_check_type(world, joint_id, JointType::Revolute);
197 let revolute = joint.revolute_mut();
198 if lower != revolute.lower_angle || upper != revolute.upper_angle {
199 revolute.lower_angle = min_float(lower, upper);
200 revolute.upper_angle = max_float(lower, upper);
201 revolute.lower_impulse = 0.0;
202 revolute.upper_impulse = 0.0;
203 }
204}
205
206pub fn revolute_joint_enable_motor(world: &mut World, joint_id: JointId, enable_motor: bool) {
208 crate::recording::record_op(world, |rec, _| {
209 crate::recording::write_joint_bool(
210 rec,
211 crate::recording::OP_REVOLUTE_ENABLE_MOTOR,
212 joint_id,
213 enable_motor,
214 )
215 });
216 let joint = get_joint_sim_check_type(world, joint_id, JointType::Revolute);
217 let revolute = joint.revolute_mut();
218 if enable_motor != revolute.enable_motor {
219 revolute.enable_motor = enable_motor;
220 revolute.motor_impulse = 0.0;
221 }
222}
223
224pub fn revolute_joint_is_motor_enabled(world: &World, joint_id: JointId) -> bool {
226 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
227 joint.revolute().enable_motor
228}
229
230pub fn revolute_joint_set_motor_speed(world: &mut World, joint_id: JointId, motor_speed: f32) {
232 crate::recording::record_op(world, |rec, _| {
233 crate::recording::write_joint_f32(
234 rec,
235 crate::recording::OP_REVOLUTE_SET_MOTOR_SPEED,
236 joint_id,
237 motor_speed,
238 )
239 });
240 let joint = get_joint_sim_check_type(world, joint_id, JointType::Revolute);
241 joint.revolute_mut().motor_speed = motor_speed;
242}
243
244pub fn revolute_joint_get_motor_speed(world: &World, joint_id: JointId) -> f32 {
246 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
247 joint.revolute().motor_speed
248}
249
250pub fn revolute_joint_get_motor_torque(world: &World, joint_id: JointId) -> f32 {
252 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
253 world.inv_h * joint.revolute().motor_impulse
254}
255
256pub fn revolute_joint_set_max_motor_torque(world: &mut World, joint_id: JointId, torque: f32) {
258 crate::recording::record_op(world, |rec, _| {
259 crate::recording::write_joint_f32(
260 rec,
261 crate::recording::OP_REVOLUTE_SET_MAX_MOTOR_TORQUE,
262 joint_id,
263 torque,
264 )
265 });
266 let joint = get_joint_sim_check_type(world, joint_id, JointType::Revolute);
267 joint.revolute_mut().max_motor_torque = torque;
268}
269
270pub fn revolute_joint_get_max_motor_torque(world: &World, joint_id: JointId) -> f32 {
272 let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Revolute);
273 joint.revolute().max_motor_torque
274}
275
276pub fn get_revolute_joint_force(world: &World, base: &JointSim) -> Vec2 {
278 mul_sv(world.inv_h, base.revolute().linear_impulse)
279}
280
281pub fn get_revolute_joint_torque(world: &World, base: &JointSim) -> f32 {
283 let revolute = base.revolute();
284 world.inv_h * (revolute.motor_impulse + revolute.lower_impulse - revolute.upper_impulse)
285}
286
287pub fn prepare_revolute_joint(world: &World, base: &mut JointSim, context: &StepContext) {
289 debug_assert!(base.joint_type() == JointType::Revolute);
290
291 let id_a = base.body_id_a;
293 let id_b = base.body_id_b;
294
295 let body_a = &world.bodies[id_a as usize];
296 let body_b = &world.bodies[id_b as usize];
297
298 debug_assert!(body_a.set_index == AWAKE_SET || body_b.set_index == AWAKE_SET);
299
300 let body_sim_a =
301 &world.solver_sets[body_a.set_index as usize].body_sims[body_a.local_index as usize];
302 let body_sim_b =
303 &world.solver_sets[body_b.set_index as usize].body_sims[body_b.local_index as usize];
304
305 let m_a = body_sim_a.inv_mass;
306 let i_a = body_sim_a.inv_inertia;
307 let m_b = body_sim_b.inv_mass;
308 let i_b = body_sim_b.inv_inertia;
309
310 base.inv_mass_a = m_a;
311 base.inv_mass_b = m_b;
312 base.inv_i_a = i_a;
313 base.inv_i_b = i_b;
314
315 let local_frame_a = base.local_frame_a;
316 let local_frame_b = base.local_frame_b;
317
318 let index_a = if body_a.set_index == AWAKE_SET {
319 body_a.local_index
320 } else {
321 NULL_INDEX
322 };
323 let index_b = if body_b.set_index == AWAKE_SET {
324 body_b.local_index
325 } else {
326 NULL_INDEX
327 };
328
329 let joint = base.revolute_mut();
330
331 joint.index_a = index_a;
332 joint.index_b = index_b;
333
334 joint.frame_a.q = mul_rot(body_sim_a.transform.q, local_frame_a.q);
340 joint.frame_a.p = rotate_vector(
341 body_sim_a.transform.q,
342 sub(local_frame_a.p, body_sim_a.local_center),
343 );
344 joint.frame_b.q = mul_rot(body_sim_b.transform.q, local_frame_b.q);
345 joint.frame_b.p = rotate_vector(
346 body_sim_b.transform.q,
347 sub(local_frame_b.p, body_sim_b.local_center),
348 );
349
350 joint.delta_center = sub_pos(body_sim_b.center, body_sim_a.center);
353
354 let k = i_a + i_b;
355 joint.axial_mass = if k > 0.0 { 1.0 / k } else { 0.0 };
356
357 joint.spring_softness = make_soft(joint.hertz, joint.damping_ratio, context.h);
358
359 if !context.enable_warm_starting {
360 joint.linear_impulse = VEC2_ZERO;
361 joint.spring_impulse = 0.0;
362 joint.motor_impulse = 0.0;
363 joint.lower_impulse = 0.0;
364 joint.upper_impulse = 0.0;
365 }
366}
367
368pub fn warm_start_revolute_joint(base: &mut JointSim, states: &mut [BodyState]) {
370 debug_assert!(base.joint_type() == JointType::Revolute);
371
372 let m_a = base.inv_mass_a;
373 let m_b = base.inv_mass_b;
374 let i_a = base.inv_i_a;
375 let i_b = base.inv_i_b;
376
377 let joint = base.revolute_mut();
378
379 let mut state_a = if joint.index_a == NULL_INDEX {
381 IDENTITY_BODY_STATE
382 } else {
383 states[joint.index_a as usize]
384 };
385 let mut state_b = if joint.index_b == NULL_INDEX {
386 IDENTITY_BODY_STATE
387 } else {
388 states[joint.index_b as usize]
389 };
390
391 let r_a = rotate_vector(state_a.delta_rotation, joint.frame_a.p);
392 let r_b = rotate_vector(state_b.delta_rotation, joint.frame_b.p);
393
394 let axial_impulse =
395 joint.spring_impulse + joint.motor_impulse + joint.lower_impulse - joint.upper_impulse;
396
397 if state_a.flags & body_flags::DYNAMIC_FLAG != 0 {
398 state_a.linear_velocity = mul_sub(state_a.linear_velocity, m_a, joint.linear_impulse);
399 state_a.angular_velocity -= i_a * (cross(r_a, joint.linear_impulse) + axial_impulse);
400 states[joint.index_a as usize] = state_a;
401 }
402
403 if state_b.flags & body_flags::DYNAMIC_FLAG != 0 {
404 state_b.linear_velocity = mul_add(state_b.linear_velocity, m_b, joint.linear_impulse);
405 state_b.angular_velocity += i_b * (cross(r_b, joint.linear_impulse) + axial_impulse);
406 states[joint.index_b as usize] = state_b;
407 }
408}
409
410pub fn solve_revolute_joint(
412 base: &mut JointSim,
413 context: &StepContext,
414 states: &mut [BodyState],
415 use_bias: bool,
416) {
417 debug_assert!(base.joint_type() == JointType::Revolute);
418
419 let m_a = base.inv_mass_a;
420 let m_b = base.inv_mass_b;
421 let i_a = base.inv_i_a;
422 let i_b = base.inv_i_b;
423 let constraint_softness = base.constraint_softness;
424
425 let joint = base.revolute_mut();
426
427 let mut state_a = if joint.index_a == NULL_INDEX {
429 IDENTITY_BODY_STATE
430 } else {
431 states[joint.index_a as usize]
432 };
433 let mut state_b = if joint.index_b == NULL_INDEX {
434 IDENTITY_BODY_STATE
435 } else {
436 states[joint.index_b as usize]
437 };
438
439 let mut v_a = state_a.linear_velocity;
440 let mut w_a = state_a.angular_velocity;
441 let mut v_b = state_b.linear_velocity;
442 let mut w_b = state_b.angular_velocity;
443
444 let q_a = mul_rot(state_a.delta_rotation, joint.frame_a.q);
445 let q_b = mul_rot(state_b.delta_rotation, joint.frame_b.q);
446 let rel_q = inv_mul_rot(q_a, q_b);
447
448 let fixed_rotation = i_a + i_b == 0.0;
449
450 if joint.enable_spring && !fixed_rotation {
452 let joint_angle = rot_get_angle(rel_q);
453 let joint_angle_delta = unwind_angle(joint_angle - joint.target_angle);
454
455 let c = joint_angle_delta;
456 let bias = joint.spring_softness.bias_rate * c;
457 let mass_scale = joint.spring_softness.mass_scale;
458 let impulse_scale = joint.spring_softness.impulse_scale;
459
460 let c_dot = w_b - w_a;
461 let impulse =
462 -mass_scale * joint.axial_mass * (c_dot + bias) - impulse_scale * joint.spring_impulse;
463 joint.spring_impulse += impulse;
464
465 w_a -= i_a * impulse;
466 w_b += i_b * impulse;
467 }
468
469 if joint.enable_motor && !fixed_rotation {
471 let c_dot = w_b - w_a - joint.motor_speed;
472 let mut impulse = -joint.axial_mass * c_dot;
473 let old_impulse = joint.motor_impulse;
474 let max_impulse = context.h * joint.max_motor_torque;
475 joint.motor_impulse = clamp_float(joint.motor_impulse + impulse, -max_impulse, max_impulse);
476 impulse = joint.motor_impulse - old_impulse;
477
478 w_a -= i_a * impulse;
479 w_b += i_b * impulse;
480 }
481
482 if joint.enable_limit && !fixed_rotation {
483 let joint_angle = rot_get_angle(rel_q);
484
485 {
487 let c = joint_angle - joint.lower_angle;
488 let mut bias = 0.0;
489 let mut mass_scale = 1.0;
490 let mut impulse_scale = 0.0;
491 if c > 0.0 {
492 bias = c * context.inv_h;
494 } else if use_bias {
495 bias = constraint_softness.bias_rate * c;
496 mass_scale = constraint_softness.mass_scale;
497 impulse_scale = constraint_softness.impulse_scale;
498 }
499
500 let c_dot = w_b - w_a;
501 let old_impulse = joint.lower_impulse;
502 let mut impulse =
503 -mass_scale * joint.axial_mass * (c_dot + bias) - impulse_scale * old_impulse;
504 joint.lower_impulse = max_float(old_impulse + impulse, 0.0);
505 impulse = joint.lower_impulse - old_impulse;
506
507 w_a -= i_a * impulse;
508 w_b += i_b * impulse;
509 }
510
511 {
516 let c = joint.upper_angle - joint_angle;
517 let mut bias = 0.0;
518 let mut mass_scale = 1.0;
519 let mut impulse_scale = 0.0;
520 if c > 0.0 {
521 bias = c * context.inv_h;
523 } else if use_bias {
524 bias = constraint_softness.bias_rate * c;
525 mass_scale = constraint_softness.mass_scale;
526 impulse_scale = constraint_softness.impulse_scale;
527 }
528
529 let c_dot = w_a - w_b;
531 let old_impulse = joint.upper_impulse;
532 let mut impulse =
533 -mass_scale * joint.axial_mass * (c_dot + bias) - impulse_scale * old_impulse;
534 joint.upper_impulse = max_float(old_impulse + impulse, 0.0);
535 impulse = joint.upper_impulse - old_impulse;
536
537 w_a += i_a * impulse;
539 w_b -= i_b * impulse;
540 }
541 }
542
543 {
545 let r_a = rotate_vector(state_a.delta_rotation, joint.frame_a.p);
552 let r_b = rotate_vector(state_b.delta_rotation, joint.frame_b.p);
553
554 let c_dot = sub(add(v_b, cross_sv(w_b, r_b)), add(v_a, cross_sv(w_a, r_a)));
555
556 let mut bias = VEC2_ZERO;
557 let mut mass_scale = 1.0;
558 let mut impulse_scale = 0.0;
559 if use_bias {
560 let dc_a = state_a.delta_position;
561 let dc_b = state_b.delta_position;
562
563 let separation = add(add(sub(dc_b, dc_a), sub(r_b, r_a)), joint.delta_center);
564 bias = mul_sv(constraint_softness.bias_rate, separation);
565 mass_scale = constraint_softness.mass_scale;
566 impulse_scale = constraint_softness.impulse_scale;
567 }
568
569 let mut k = MAT22_ZERO;
570 k.cx.x = m_a + m_b + r_a.y * r_a.y * i_a + r_b.y * r_b.y * i_b;
571 k.cy.x = -r_a.y * r_a.x * i_a - r_b.y * r_b.x * i_b;
572 k.cx.y = k.cy.x;
573 k.cy.y = m_a + m_b + r_a.x * r_a.x * i_a + r_b.x * r_b.x * i_b;
574 let b = solve_22(k, add(c_dot, bias));
575
576 let impulse = Vec2 {
577 x: -mass_scale * b.x - impulse_scale * joint.linear_impulse.x,
578 y: -mass_scale * b.y - impulse_scale * joint.linear_impulse.y,
579 };
580 joint.linear_impulse.x += impulse.x;
581 joint.linear_impulse.y += impulse.y;
582
583 v_a = mul_sub(v_a, m_a, impulse);
584 w_a -= i_a * cross(r_a, impulse);
585 v_b = mul_add(v_b, m_b, impulse);
586 w_b += i_b * cross(r_b, impulse);
587 }
588
589 if state_a.flags & body_flags::DYNAMIC_FLAG != 0 {
590 state_a.linear_velocity = v_a;
591 state_a.angular_velocity = w_a;
592 states[joint.index_a as usize] = state_a;
593 }
594
595 if state_b.flags & body_flags::DYNAMIC_FLAG != 0 {
596 state_b.linear_velocity = v_b;
597 state_b.angular_velocity = w_b;
598 states[joint.index_b as usize] = state_b;
599 }
600}
601
602pub fn draw_revolute_joint(
604 draw: &mut dyn crate::debug_draw::DebugDraw,
605 base: &JointSim,
606 transform_a: WorldTransform,
607 transform_b: WorldTransform,
608 draw_scale: f32,
609) {
610 use crate::debug_draw::HexColor;
611 use crate::math_functions::{
612 make_rot, mul_rot, offset_pos, offset_world_transform, relative_angle, rotate_vector, Vec2,
613 PI,
614 };
615
616 debug_assert!(base.joint_type() == JointType::Revolute);
617
618 let joint = base.revolute();
619
620 let frame_a = offset_world_transform(transform_a, base.local_frame_a);
621 let frame_b = offset_world_transform(transform_b, base.local_frame_b);
622
623 let radius = 0.25 * draw_scale;
624 draw.draw_circle(frame_b.p, radius, HexColor::GRAY);
625
626 let rx = Vec2 { x: radius, y: 0.0 };
627 let mut r = rotate_vector(frame_a.q, rx);
628 draw.draw_line(frame_a.p, offset_pos(frame_a.p, r), HexColor::GRAY);
629
630 r = rotate_vector(frame_b.q, rx);
631 draw.draw_line(frame_b.p, offset_pos(frame_b.p, r), HexColor::BLUE);
632
633 if draw.draw_joint_extras() {
634 let joint_angle = relative_angle(frame_a.q, frame_b.q);
635 let buffer = format!(" {:.1} deg", 180.0 * joint_angle / PI);
636 draw.draw_string(offset_pos(frame_a.p, r), &buffer, HexColor::WHITE);
637 }
638
639 let lower_angle = joint.lower_angle;
640 let upper_angle = joint.upper_angle;
641
642 if joint.enable_limit {
643 let rot_lo = mul_rot(frame_a.q, make_rot(lower_angle));
644 let rlo = rotate_vector(rot_lo, rx);
645
646 let rot_hi = mul_rot(frame_a.q, make_rot(upper_angle));
647 let rhi = rotate_vector(rot_hi, rx);
648
649 draw.draw_line(frame_b.p, offset_pos(frame_b.p, rlo), HexColor::GREEN);
650 draw.draw_line(frame_b.p, offset_pos(frame_b.p, rhi), HexColor::RED);
651 }
652
653 if joint.enable_spring {
654 let q = mul_rot(frame_a.q, make_rot(joint.target_angle));
655 let v = rotate_vector(q, rx);
656 draw.draw_line(frame_b.p, offset_pos(frame_b.p, v), HexColor::VIOLET);
657 }
658
659 let color = HexColor::GOLD;
660 draw.draw_line(transform_a.p, frame_a.p, color);
661 draw.draw_line(frame_a.p, frame_b.p, color);
662 draw.draw_line(transform_b.p, frame_b.p, color);
663}