1use 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
24pub 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
42pub 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
48pub 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
62pub 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
68pub 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
86pub 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
92pub 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
111pub 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
117pub 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
123pub 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
129pub 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
152pub 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
170pub 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
176pub 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
190pub 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
196pub 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
202pub 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
216pub 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
222pub 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
239pub fn get_wheel_joint_torque(world: &World, base: &JointSim) -> f32 {
241 world.inv_h * base.wheel().motor_impulse
242}
243
244pub fn prepare_wheel_joint(world: &World, base: &mut JointSim, context: &StepContext) {
262 debug_assert!(base.joint_type() == JointType::Wheel);
263
264 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 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 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 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 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
359pub 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 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
423pub 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 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 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 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 if joint.enable_spring {
492 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 {
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 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 {
552 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 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 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 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 {
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
632pub 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}