Skip to main content

box2d_rust/
revolute_joint.rs

1// Port of revolute_joint.c: public accessors, force/torque reporting, and
2// the prepare/warm-start/solve simulation functions.
3//
4// Same conventions as distance_joint.rs: the world is passed explicitly,
5// B2_REC recording is not ported, sim functions copy body states out and back
6// writing only the fields the C guards with b2_dynamicFlag.
7//
8// SPDX-FileCopyrightText: 2023 Erin Catto
9// SPDX-License-Identifier: MIT
10//
11// bring-up: prepare/warm-start/solve are called by the solver slice.
12
13use 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
27// Point-to-point constraint
28// C = pB - pA
29// Cdot = vB - vA
30//      = vB + cross(wB, rB) - vA - cross(wA, rA)
31// J = [-E -skew(rA) E skew(rB) ]
32//
33// Identity used:
34// w k % (rx i + ry j) = w * (-ry i + rx j)
35//
36// Motor constraint
37// Cdot = wB - wA
38// J = [0 0 -1 0 0 1]
39// K = invIA + invIB
40
41/// (b2RevoluteJoint_EnableSpring)
42pub 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
59/// (b2RevoluteJoint_IsSpringEnabled)
60pub 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
65/// (b2RevoluteJoint_SetSpringHertz)
66pub 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
79/// (b2RevoluteJoint_GetSpringHertz)
80pub 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
85/// (b2RevoluteJoint_SetSpringDampingRatio)
86pub 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
103/// (b2RevoluteJoint_GetSpringDampingRatio)
104pub 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
109/// (b2RevoluteJoint_SetTargetAngle)
110pub 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
123/// (b2RevoluteJoint_GetTargetAngle)
124pub 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
129/// (b2RevoluteJoint_GetAngle)
130pub 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
144/// (b2RevoluteJoint_EnableLimit)
145pub 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
163/// (b2RevoluteJoint_IsLimitEnabled)
164pub 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
169/// (b2RevoluteJoint_GetLowerLimit)
170pub 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
175/// (b2RevoluteJoint_GetUpperLimit)
176pub 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
181/// (b2RevoluteJoint_SetLimits)
182pub 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
206/// (b2RevoluteJoint_EnableMotor)
207pub 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
224/// (b2RevoluteJoint_IsMotorEnabled)
225pub 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
230/// (b2RevoluteJoint_SetMotorSpeed)
231pub 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
244/// (b2RevoluteJoint_GetMotorSpeed)
245pub 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
250/// (b2RevoluteJoint_GetMotorTorque)
251pub 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
256/// (b2RevoluteJoint_SetMaxMotorTorque)
257pub 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
270/// (b2RevoluteJoint_GetMaxMotorTorque)
271pub 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
276/// (b2GetRevoluteJointForce)
277pub fn get_revolute_joint_force(world: &World, base: &JointSim) -> Vec2 {
278    mul_sv(world.inv_h, base.revolute().linear_impulse)
279}
280
281/// (b2GetRevoluteJointTorque)
282pub 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
287/// (b2PrepareRevoluteJoint)
288pub fn prepare_revolute_joint(world: &World, base: &mut JointSim, context: &StepContext) {
289    debug_assert!(base.joint_type() == JointType::Revolute);
290
291    // chase body id to the solver set where the body lives
292    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    // Compute joint anchor frames with world space rotation, relative to
335    // center of mass. Avoid round-off here as much as possible.
336    // b2Vec2 pf = (xf.p - c) + rot(xf.q, f.p)
337    // pf = xf.p - (xf.p + rot(xf.q, lc)) + rot(xf.q, f.p)
338    // pf = rot(xf.q, f.p - lc)
339    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    // Compute the initial center delta. Incremental position updates are
351    // relative to this.
352    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
368/// (b2WarmStartRevoluteJoint)
369pub 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    // dummy state for static bodies
380    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
410/// (b2SolveRevoluteJoint)
411pub 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    // dummy state for static bodies
428    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    // Solve spring.
451    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    // Solve motor constraint.
470    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        // Lower limit
486        {
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                // speculation
493                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        // Upper limit
512        // Note: signs are flipped to keep C positive when the constraint is
513        // satisfied. This also keeps the impulse positive when the limit is
514        // active.
515        {
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                // speculation
522                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            // sign flipped on Cdot
530            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            // sign flipped on applied impulse
538            w_a += i_a * impulse;
539            w_b -= i_b * impulse;
540        }
541    }
542
543    // Solve point-to-point constraint
544    {
545        // J = [-I -r1_skew I r2_skew]
546        // r_skew = [-ry; rx]
547        // K = [ mA+r1y^2*iA+mB+r2y^2*iB,  -r1y*iA*r1x-r2y*iB*r2x]
548        //     [  -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB]
549
550        // current anchors
551        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
602/// (b2DrawRevoluteJoint)
603pub 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}