Skip to main content

box2d_rust/
wheel_joint.rs

1// Port of wheel_joint.c: public accessors, force/torque reporting, and the
2// prepare/warm-start/solve simulation functions.
3//
4// Same conventions as distance_joint.rs.
5//
6// SPDX-FileCopyrightText: 2023 Erin Catto
7// SPDX-License-Identifier: MIT
8//
9// bring-up: prepare/warm-start/solve are called by the solver slice.
10
11use crate::body::{body_flags, get_body_transform, BodyState, IDENTITY_BODY_STATE};
12use crate::core::NULL_INDEX;
13use crate::id::JointId;
14use crate::joint::{get_joint_sim_check_type, get_joint_sim_check_type_ref, JointSim, JointType};
15use crate::math_functions::WorldTransform;
16use crate::math_functions::{
17    add, clamp_float, cross, dot, left_perp, max_float, min_float, mul_add, mul_rot, mul_sub,
18    mul_sv, rotate_vector, sub, sub_pos, Vec2,
19};
20use crate::solver::{make_soft, StepContext};
21use crate::solver_set::AWAKE_SET;
22use crate::world::World;
23
24/// (b2WheelJoint_EnableSpring)
25pub fn wheel_joint_enable_spring(world: &mut World, joint_id: JointId, enable_spring: bool) {
26    crate::recording::record_op(world, |rec, _| {
27        crate::recording::write_joint_bool(
28            rec,
29            crate::recording::OP_WHEEL_ENABLE_SPRING,
30            joint_id,
31            enable_spring,
32        )
33    });
34    let joint = get_joint_sim_check_type(world, joint_id, JointType::Wheel);
35    let wheel = joint.wheel_mut();
36    if enable_spring != wheel.enable_spring {
37        wheel.enable_spring = enable_spring;
38        wheel.spring_impulse = 0.0;
39    }
40}
41
42/// (b2WheelJoint_IsSpringEnabled)
43pub fn wheel_joint_is_spring_enabled(world: &World, joint_id: JointId) -> bool {
44    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
45    joint.wheel().enable_spring
46}
47
48/// (b2WheelJoint_SetSpringHertz)
49pub fn wheel_joint_set_spring_hertz(world: &mut World, joint_id: JointId, hertz: f32) {
50    crate::recording::record_op(world, |rec, _| {
51        crate::recording::write_joint_f32(
52            rec,
53            crate::recording::OP_WHEEL_SET_SPRING_HERTZ,
54            joint_id,
55            hertz,
56        )
57    });
58    let joint = get_joint_sim_check_type(world, joint_id, JointType::Wheel);
59    joint.wheel_mut().hertz = hertz;
60}
61
62/// (b2WheelJoint_GetSpringHertz)
63pub fn wheel_joint_get_spring_hertz(world: &World, joint_id: JointId) -> f32 {
64    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
65    joint.wheel().hertz
66}
67
68/// (b2WheelJoint_SetSpringDampingRatio)
69pub fn wheel_joint_set_spring_damping_ratio(
70    world: &mut World,
71    joint_id: JointId,
72    damping_ratio: f32,
73) {
74    crate::recording::record_op(world, |rec, _| {
75        crate::recording::write_joint_f32(
76            rec,
77            crate::recording::OP_WHEEL_SET_SPRING_DAMPING_RATIO,
78            joint_id,
79            damping_ratio,
80        )
81    });
82    let joint = get_joint_sim_check_type(world, joint_id, JointType::Wheel);
83    joint.wheel_mut().damping_ratio = damping_ratio;
84}
85
86/// (b2WheelJoint_GetSpringDampingRatio)
87pub fn wheel_joint_get_spring_damping_ratio(world: &World, joint_id: JointId) -> f32 {
88    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
89    joint.wheel().damping_ratio
90}
91
92/// (b2WheelJoint_EnableLimit)
93pub fn wheel_joint_enable_limit(world: &mut World, joint_id: JointId, enable_limit: bool) {
94    crate::recording::record_op(world, |rec, _| {
95        crate::recording::write_joint_bool(
96            rec,
97            crate::recording::OP_WHEEL_ENABLE_LIMIT,
98            joint_id,
99            enable_limit,
100        )
101    });
102    let joint = get_joint_sim_check_type(world, joint_id, JointType::Wheel);
103    let wheel = joint.wheel_mut();
104    if wheel.enable_limit != enable_limit {
105        wheel.lower_impulse = 0.0;
106        wheel.upper_impulse = 0.0;
107        wheel.enable_limit = enable_limit;
108    }
109}
110
111/// (b2WheelJoint_IsLimitEnabled)
112pub fn wheel_joint_is_limit_enabled(world: &World, joint_id: JointId) -> bool {
113    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
114    joint.wheel().enable_limit
115}
116
117/// (b2WheelJoint_GetLowerLimit)
118pub fn wheel_joint_get_lower_limit(world: &World, joint_id: JointId) -> f32 {
119    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
120    joint.wheel().lower_translation
121}
122
123/// (b2WheelJoint_GetUpperLimit)
124pub fn wheel_joint_get_upper_limit(world: &World, joint_id: JointId) -> f32 {
125    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
126    joint.wheel().upper_translation
127}
128
129/// (b2WheelJoint_SetLimits)
130pub fn wheel_joint_set_limits(world: &mut World, joint_id: JointId, lower: f32, upper: f32) {
131    crate::recording::record_op(world, |rec, _| {
132        crate::recording::write_joint_f32_pair(
133            rec,
134            crate::recording::OP_WHEEL_SET_LIMITS,
135            joint_id,
136            lower,
137            upper,
138        )
139    });
140    debug_assert!(lower <= upper);
141
142    let joint = get_joint_sim_check_type(world, joint_id, JointType::Wheel);
143    let wheel = joint.wheel_mut();
144    if lower != wheel.lower_translation || upper != wheel.upper_translation {
145        wheel.lower_translation = min_float(lower, upper);
146        wheel.upper_translation = max_float(lower, upper);
147        wheel.lower_impulse = 0.0;
148        wheel.upper_impulse = 0.0;
149    }
150}
151
152/// (b2WheelJoint_EnableMotor)
153pub fn wheel_joint_enable_motor(world: &mut World, joint_id: JointId, enable_motor: bool) {
154    crate::recording::record_op(world, |rec, _| {
155        crate::recording::write_joint_bool(
156            rec,
157            crate::recording::OP_WHEEL_ENABLE_MOTOR,
158            joint_id,
159            enable_motor,
160        )
161    });
162    let joint = get_joint_sim_check_type(world, joint_id, JointType::Wheel);
163    let wheel = joint.wheel_mut();
164    if wheel.enable_motor != enable_motor {
165        wheel.motor_impulse = 0.0;
166        wheel.enable_motor = enable_motor;
167    }
168}
169
170/// (b2WheelJoint_IsMotorEnabled)
171pub fn wheel_joint_is_motor_enabled(world: &World, joint_id: JointId) -> bool {
172    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
173    joint.wheel().enable_motor
174}
175
176/// (b2WheelJoint_SetMotorSpeed)
177pub fn wheel_joint_set_motor_speed(world: &mut World, joint_id: JointId, motor_speed: f32) {
178    crate::recording::record_op(world, |rec, _| {
179        crate::recording::write_joint_f32(
180            rec,
181            crate::recording::OP_WHEEL_SET_MOTOR_SPEED,
182            joint_id,
183            motor_speed,
184        )
185    });
186    let joint = get_joint_sim_check_type(world, joint_id, JointType::Wheel);
187    joint.wheel_mut().motor_speed = motor_speed;
188}
189
190/// (b2WheelJoint_GetMotorSpeed)
191pub fn wheel_joint_get_motor_speed(world: &World, joint_id: JointId) -> f32 {
192    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
193    joint.wheel().motor_speed
194}
195
196/// (b2WheelJoint_GetMotorTorque)
197pub fn wheel_joint_get_motor_torque(world: &World, joint_id: JointId) -> f32 {
198    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
199    world.inv_h * joint.wheel().motor_impulse
200}
201
202/// (b2WheelJoint_SetMaxMotorTorque)
203pub fn wheel_joint_set_max_motor_torque(world: &mut World, joint_id: JointId, torque: f32) {
204    crate::recording::record_op(world, |rec, _| {
205        crate::recording::write_joint_f32(
206            rec,
207            crate::recording::OP_WHEEL_SET_MAX_MOTOR_TORQUE,
208            joint_id,
209            torque,
210        )
211    });
212    let joint = get_joint_sim_check_type(world, joint_id, JointType::Wheel);
213    joint.wheel_mut().max_motor_torque = torque;
214}
215
216/// (b2WheelJoint_GetMaxMotorTorque)
217pub fn wheel_joint_get_max_motor_torque(world: &World, joint_id: JointId) -> f32 {
218    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Wheel);
219    joint.wheel().max_motor_torque
220}
221
222/// (b2GetWheelJointForce)
223pub fn get_wheel_joint_force(world: &World, base: &JointSim) -> Vec2 {
224    let q_a = get_body_transform(world, base.body_id_a).q;
225
226    let local_axis_a = rotate_vector(base.local_frame_a.q, Vec2 { x: 1.0, y: 0.0 });
227    let axis_a = rotate_vector(q_a, local_axis_a);
228    let perp_a = left_perp(axis_a);
229
230    let joint = base.wheel();
231
232    let perp_force = world.inv_h * joint.perp_impulse;
233    let axial_force =
234        world.inv_h * (joint.spring_impulse + joint.lower_impulse - joint.upper_impulse);
235
236    add(mul_sv(perp_force, perp_a), mul_sv(axial_force, axis_a))
237}
238
239/// (b2GetWheelJointTorque)
240pub fn get_wheel_joint_torque(world: &World, base: &JointSim) -> f32 {
241    world.inv_h * base.wheel().motor_impulse
242}
243
244// Linear constraint (point-to-line)
245// d = pB - pA = xB + rB - xA - rA
246// C = dot(ay, d)
247// Cdot = dot(d, cross(wA, ay)) + dot(ay, vB + cross(wB, rB) - vA - cross(wA, rA))
248//      = -dot(ay, vA) - dot(cross(d + rA, ay), wA) + dot(ay, vB) + dot(cross(rB, ay), vB)
249// J = [-ay, -cross(d + rA, ay), ay, cross(rB, ay)]
250//
251// Spring linear constraint
252// C = dot(ax, d)
253// Cdot = = -dot(ax, vA) - dot(cross(d + rA, ax), wA) + dot(ax, vB) + dot(cross(rB, ax), vB)
254// J = [-ax -cross(d+rA, ax) ax cross(rB, ax)]
255//
256// Motor rotational constraint
257// Cdot = wB - wA
258// J = [0 0 -1 0 0 1]
259
260/// (b2PrepareWheelJoint)
261pub fn prepare_wheel_joint(world: &World, base: &mut JointSim, context: &StepContext) {
262    debug_assert!(base.joint_type() == JointType::Wheel);
263
264    // chase body id to the solver set where the body lives
265    let id_a = base.body_id_a;
266    let id_b = base.body_id_b;
267
268    let body_a = &world.bodies[id_a as usize];
269    let body_b = &world.bodies[id_b as usize];
270
271    debug_assert!(body_a.set_index == AWAKE_SET || body_b.set_index == AWAKE_SET);
272
273    let body_sim_a =
274        &world.solver_sets[body_a.set_index as usize].body_sims[body_a.local_index as usize];
275    let body_sim_b =
276        &world.solver_sets[body_b.set_index as usize].body_sims[body_b.local_index as usize];
277
278    let m_a = body_sim_a.inv_mass;
279    let i_a = body_sim_a.inv_inertia;
280    let m_b = body_sim_b.inv_mass;
281    let i_b = body_sim_b.inv_inertia;
282
283    base.inv_mass_a = m_a;
284    base.inv_mass_b = m_b;
285    base.inv_i_a = i_a;
286    base.inv_i_b = i_b;
287
288    let local_frame_a = base.local_frame_a;
289    let local_frame_b = base.local_frame_b;
290
291    let index_a = if body_a.set_index == AWAKE_SET {
292        body_a.local_index
293    } else {
294        NULL_INDEX
295    };
296    let index_b = if body_b.set_index == AWAKE_SET {
297        body_b.local_index
298    } else {
299        NULL_INDEX
300    };
301
302    let joint = base.wheel_mut();
303
304    joint.index_a = index_a;
305    joint.index_b = index_b;
306
307    // Compute joint anchor frames with world space rotation, relative to
308    // center of mass
309    joint.frame_a.q = mul_rot(body_sim_a.transform.q, local_frame_a.q);
310    joint.frame_a.p = rotate_vector(
311        body_sim_a.transform.q,
312        sub(local_frame_a.p, body_sim_a.local_center),
313    );
314    joint.frame_b.q = mul_rot(body_sim_b.transform.q, local_frame_b.q);
315    joint.frame_b.p = rotate_vector(
316        body_sim_b.transform.q,
317        sub(local_frame_b.p, body_sim_b.local_center),
318    );
319
320    // Compute the initial center delta. Incremental position updates are
321    // relative to this.
322    joint.delta_center = sub_pos(body_sim_b.center, body_sim_a.center);
323
324    let r_a = joint.frame_a.p;
325    let r_b = joint.frame_b.p;
326
327    let d = add(joint.delta_center, sub(r_b, r_a));
328    let axis_a = rotate_vector(joint.frame_a.q, Vec2 { x: 1.0, y: 0.0 });
329    let perp_a = left_perp(axis_a);
330
331    // perpendicular constraint (keep wheel on line)
332    let s1 = cross(add(d, r_a), perp_a);
333    let s2 = cross(r_b, perp_a);
334
335    let kp = m_a + m_b + i_a * s1 * s1 + i_b * s2 * s2;
336    joint.perp_mass = if kp > 0.0 { 1.0 / kp } else { 0.0 };
337
338    // spring constraint
339    let a1 = cross(add(d, r_a), axis_a);
340    let a2 = cross(r_b, axis_a);
341
342    let ka = m_a + m_b + i_a * a1 * a1 + i_b * a2 * a2;
343    joint.axial_mass = if ka > 0.0 { 1.0 / ka } else { 0.0 };
344
345    joint.spring_softness = make_soft(joint.hertz, joint.damping_ratio, context.h);
346
347    let km = i_a + i_b;
348    joint.motor_mass = if km > 0.0 { 1.0 / km } else { 0.0 };
349
350    if !context.enable_warm_starting {
351        joint.perp_impulse = 0.0;
352        joint.spring_impulse = 0.0;
353        joint.motor_impulse = 0.0;
354        joint.lower_impulse = 0.0;
355        joint.upper_impulse = 0.0;
356    }
357}
358
359/// (b2WarmStartWheelJoint)
360pub fn warm_start_wheel_joint(base: &mut JointSim, states: &mut [BodyState]) {
361    debug_assert!(base.joint_type() == JointType::Wheel);
362
363    let m_a = base.inv_mass_a;
364    let m_b = base.inv_mass_b;
365    let i_a = base.inv_i_a;
366    let i_b = base.inv_i_b;
367
368    let joint = base.wheel_mut();
369
370    // dummy state for static bodies
371    let mut state_a = if joint.index_a == NULL_INDEX {
372        IDENTITY_BODY_STATE
373    } else {
374        states[joint.index_a as usize]
375    };
376    let mut state_b = if joint.index_b == NULL_INDEX {
377        IDENTITY_BODY_STATE
378    } else {
379        states[joint.index_b as usize]
380    };
381
382    let r_a = rotate_vector(state_a.delta_rotation, joint.frame_a.p);
383    let r_b = rotate_vector(state_b.delta_rotation, joint.frame_b.p);
384
385    let d = add(
386        add(
387            sub(state_b.delta_position, state_a.delta_position),
388            joint.delta_center,
389        ),
390        sub(r_b, r_a),
391    );
392    let mut axis_a = rotate_vector(joint.frame_a.q, Vec2 { x: 1.0, y: 0.0 });
393    axis_a = rotate_vector(state_a.delta_rotation, axis_a);
394    let perp_a = left_perp(axis_a);
395
396    let a1 = cross(add(d, r_a), axis_a);
397    let a2 = cross(r_b, axis_a);
398    let s1 = cross(add(d, r_a), perp_a);
399    let s2 = cross(r_b, perp_a);
400
401    let axial_impulse = joint.spring_impulse + joint.lower_impulse - joint.upper_impulse;
402
403    let p = add(
404        mul_sv(axial_impulse, axis_a),
405        mul_sv(joint.perp_impulse, perp_a),
406    );
407    let l_a = axial_impulse * a1 + joint.perp_impulse * s1 + joint.motor_impulse;
408    let l_b = axial_impulse * a2 + joint.perp_impulse * s2 + joint.motor_impulse;
409
410    if state_a.flags & body_flags::DYNAMIC_FLAG != 0 {
411        state_a.linear_velocity = mul_sub(state_a.linear_velocity, m_a, p);
412        state_a.angular_velocity -= i_a * l_a;
413        states[joint.index_a as usize] = state_a;
414    }
415
416    if state_b.flags & body_flags::DYNAMIC_FLAG != 0 {
417        state_b.linear_velocity = mul_add(state_b.linear_velocity, m_b, p);
418        state_b.angular_velocity += i_b * l_b;
419        states[joint.index_b as usize] = state_b;
420    }
421}
422
423/// (b2SolveWheelJoint)
424pub fn solve_wheel_joint(
425    base: &mut JointSim,
426    context: &StepContext,
427    states: &mut [BodyState],
428    use_bias: bool,
429) {
430    debug_assert!(base.joint_type() == JointType::Wheel);
431
432    let m_a = base.inv_mass_a;
433    let m_b = base.inv_mass_b;
434    let i_a = base.inv_i_a;
435    let i_b = base.inv_i_b;
436    let constraint_softness = base.constraint_softness;
437
438    let joint = base.wheel_mut();
439
440    // dummy state for static bodies
441    let mut state_a = if joint.index_a == NULL_INDEX {
442        IDENTITY_BODY_STATE
443    } else {
444        states[joint.index_a as usize]
445    };
446    let mut state_b = if joint.index_b == NULL_INDEX {
447        IDENTITY_BODY_STATE
448    } else {
449        states[joint.index_b as usize]
450    };
451
452    let mut v_a = state_a.linear_velocity;
453    let mut w_a = state_a.angular_velocity;
454    let mut v_b = state_b.linear_velocity;
455    let mut w_b = state_b.angular_velocity;
456
457    let fixed_rotation = i_a + i_b == 0.0;
458
459    // current anchors
460    let r_a = rotate_vector(state_a.delta_rotation, joint.frame_a.p);
461    let r_b = rotate_vector(state_b.delta_rotation, joint.frame_b.p);
462
463    let d = add(
464        add(
465            sub(state_b.delta_position, state_a.delta_position),
466            joint.delta_center,
467        ),
468        sub(r_b, r_a),
469    );
470    let mut axis_a = rotate_vector(joint.frame_a.q, Vec2 { x: 1.0, y: 0.0 });
471    axis_a = rotate_vector(state_a.delta_rotation, axis_a);
472    let translation = dot(axis_a, d);
473
474    let a1 = cross(add(d, r_a), axis_a);
475    let a2 = cross(r_b, axis_a);
476
477    // motor constraint
478    if joint.enable_motor && !fixed_rotation {
479        let c_dot = w_b - w_a - joint.motor_speed;
480        let mut impulse = -joint.motor_mass * c_dot;
481        let old_impulse = joint.motor_impulse;
482        let max_impulse = context.h * joint.max_motor_torque;
483        joint.motor_impulse = clamp_float(joint.motor_impulse + impulse, -max_impulse, max_impulse);
484        impulse = joint.motor_impulse - old_impulse;
485
486        w_a -= i_a * impulse;
487        w_b += i_b * impulse;
488    }
489
490    // spring constraint
491    if joint.enable_spring {
492        // This is a real spring and should be applied even during relax
493        let c = translation;
494        let bias = joint.spring_softness.bias_rate * c;
495        let mass_scale = joint.spring_softness.mass_scale;
496        let impulse_scale = joint.spring_softness.impulse_scale;
497
498        let c_dot = dot(axis_a, sub(v_b, v_a)) + a2 * w_b - a1 * w_a;
499        let impulse =
500            -mass_scale * joint.axial_mass * (c_dot + bias) - impulse_scale * joint.spring_impulse;
501        joint.spring_impulse += impulse;
502
503        let p = mul_sv(impulse, axis_a);
504        let l_a = impulse * a1;
505        let l_b = impulse * a2;
506
507        v_a = mul_sub(v_a, m_a, p);
508        w_a -= i_a * l_a;
509        v_b = mul_add(v_b, m_b, p);
510        w_b += i_b * l_b;
511    }
512
513    if joint.enable_limit {
514        // Lower limit
515        {
516            let c = translation - joint.lower_translation;
517            let mut bias = 0.0;
518            let mut mass_scale = 1.0;
519            let mut impulse_scale = 0.0;
520
521            if c > 0.0 {
522                // speculation
523                bias = c * context.inv_h;
524            } else if use_bias {
525                bias = constraint_softness.bias_rate * c;
526                mass_scale = constraint_softness.mass_scale;
527                impulse_scale = constraint_softness.impulse_scale;
528            }
529
530            let c_dot = dot(axis_a, sub(v_b, v_a)) + a2 * w_b - a1 * w_a;
531            let mut impulse = -mass_scale * joint.axial_mass * (c_dot + bias)
532                - impulse_scale * joint.lower_impulse;
533            let old_impulse = joint.lower_impulse;
534            joint.lower_impulse = max_float(old_impulse + impulse, 0.0);
535            impulse = joint.lower_impulse - old_impulse;
536
537            let p = mul_sv(impulse, axis_a);
538            let l_a = impulse * a1;
539            let l_b = impulse * a2;
540
541            v_a = mul_sub(v_a, m_a, p);
542            w_a -= i_a * l_a;
543            v_b = mul_add(v_b, m_b, p);
544            w_b += i_b * l_b;
545        }
546
547        // Upper limit
548        // Note: signs are flipped to keep C positive when the constraint is
549        // satisfied. This also keeps the impulse positive when the limit is
550        // active.
551        {
552            // sign flipped
553            let c = joint.upper_translation - translation;
554            let mut bias = 0.0;
555            let mut mass_scale = 1.0;
556            let mut impulse_scale = 0.0;
557
558            if c > 0.0 {
559                // speculation
560                bias = c * context.inv_h;
561            } else if use_bias {
562                bias = constraint_softness.bias_rate * c;
563                mass_scale = constraint_softness.mass_scale;
564                impulse_scale = constraint_softness.impulse_scale;
565            }
566
567            // sign flipped on Cdot
568            let c_dot = dot(axis_a, sub(v_a, v_b)) + a1 * w_a - a2 * w_b;
569            let mut impulse = -mass_scale * joint.axial_mass * (c_dot + bias)
570                - impulse_scale * joint.upper_impulse;
571            let old_impulse = joint.upper_impulse;
572            joint.upper_impulse = max_float(old_impulse + impulse, 0.0);
573            impulse = joint.upper_impulse - old_impulse;
574
575            let p = mul_sv(impulse, axis_a);
576            let l_a = impulse * a1;
577            let l_b = impulse * a2;
578
579            // sign flipped on applied impulse
580            v_a = mul_add(v_a, m_a, p);
581            w_a += i_a * l_a;
582            v_b = mul_sub(v_b, m_b, p);
583            w_b -= i_b * l_b;
584        }
585    }
586
587    // point to line constraint
588    {
589        let perp_a = left_perp(axis_a);
590
591        let mut bias = 0.0;
592        let mut mass_scale = 1.0;
593        let mut impulse_scale = 0.0;
594        if use_bias {
595            let c = dot(perp_a, d);
596            bias = constraint_softness.bias_rate * c;
597            mass_scale = constraint_softness.mass_scale;
598            impulse_scale = constraint_softness.impulse_scale;
599        }
600
601        let s1 = cross(add(d, r_a), perp_a);
602        let s2 = cross(r_b, perp_a);
603        let c_dot = dot(perp_a, sub(v_b, v_a)) + s2 * w_b - s1 * w_a;
604
605        let impulse =
606            -mass_scale * joint.perp_mass * (c_dot + bias) - impulse_scale * joint.perp_impulse;
607        joint.perp_impulse += impulse;
608
609        let p = mul_sv(impulse, perp_a);
610        let l_a = impulse * s1;
611        let l_b = impulse * s2;
612
613        v_a = mul_sub(v_a, m_a, p);
614        w_a -= i_a * l_a;
615        v_b = mul_add(v_b, m_b, p);
616        w_b += i_b * l_b;
617    }
618
619    if state_a.flags & body_flags::DYNAMIC_FLAG != 0 {
620        state_a.linear_velocity = v_a;
621        state_a.angular_velocity = w_a;
622        states[joint.index_a as usize] = state_a;
623    }
624
625    if state_b.flags & body_flags::DYNAMIC_FLAG != 0 {
626        state_b.linear_velocity = v_b;
627        state_b.angular_velocity = w_b;
628        states[joint.index_b as usize] = state_b;
629    }
630}
631
632/// (b2DrawWheelJoint)
633pub fn draw_wheel_joint(
634    draw: &mut dyn crate::debug_draw::DebugDraw,
635    base: &JointSim,
636    transform_a: WorldTransform,
637    transform_b: WorldTransform,
638    draw_scale: f32,
639) {
640    use crate::debug_draw::HexColor;
641    use crate::math_functions::{
642        left_perp, mul_sv, neg, offset_pos, offset_world_transform, rotate_vector, Vec2,
643    };
644
645    debug_assert!(base.joint_type() == JointType::Wheel);
646
647    let joint = base.wheel();
648
649    let frame_a = offset_world_transform(transform_a, base.local_frame_a);
650    let frame_b = offset_world_transform(transform_b, base.local_frame_b);
651    let axis_a = rotate_vector(frame_a.q, Vec2 { x: 1.0, y: 0.0 });
652
653    let c1 = HexColor::GRAY;
654    let c2 = HexColor::GREEN;
655    let c3 = HexColor::RED;
656    let c4 = HexColor::DIM_GRAY;
657    let c5 = HexColor::BLUE;
658
659    draw.draw_line(frame_a.p, frame_b.p, c5);
660
661    if joint.enable_limit {
662        let lower = offset_pos(frame_a.p, mul_sv(joint.lower_translation, axis_a));
663        let upper = offset_pos(frame_a.p, mul_sv(joint.upper_translation, axis_a));
664        let perp = left_perp(axis_a);
665        draw.draw_line(lower, upper, c1);
666        draw.draw_line(
667            offset_pos(lower, mul_sv(-0.1 * draw_scale, perp)),
668            offset_pos(lower, mul_sv(0.1 * draw_scale, perp)),
669            c2,
670        );
671        draw.draw_line(
672            offset_pos(upper, mul_sv(-0.1 * draw_scale, perp)),
673            offset_pos(upper, mul_sv(0.1 * draw_scale, perp)),
674            c3,
675        );
676    } else {
677        draw.draw_line(
678            offset_pos(frame_a.p, neg(axis_a)),
679            offset_pos(frame_a.p, axis_a),
680            c1,
681        );
682    }
683
684    draw.draw_point(frame_a.p, 5.0, c1);
685    draw.draw_point(frame_b.p, 5.0, c4);
686}