1use crate::dynamics::{RigidBody, RigidBodyHandle, RigidBodySet};
4use crate::geometry::{ColliderHandle, ColliderSet, Ray};
5use crate::math::{Point, Real, Rotation, Vector};
6use crate::pipeline::QueryPipeline;
7use crate::prelude::QueryPipelineMut;
8use crate::utils::{SimdCross, SimdDot};
9
10pub struct DynamicRayCastVehicleController {
12 wheels: Vec<Wheel>,
13 forward_ws: Vec<Vector<Real>>,
14 axle: Vec<Vector<Real>>,
15 pub current_vehicle_speed: Real,
17
18 pub chassis: RigidBodyHandle,
20 pub index_up_axis: usize,
22 pub index_forward_axis: usize,
24}
25
26#[derive(Copy, Clone, Debug, PartialEq)]
27pub struct WheelTuning {
29 pub suspension_stiffness: Real,
33 pub suspension_compression: Real,
35 pub suspension_damping: Real,
39 pub max_suspension_travel: Real,
41 pub side_friction_stiffness: Real,
43 pub friction_slip: Real,
48 pub max_suspension_force: Real,
50}
51
52impl Default for WheelTuning {
53 fn default() -> Self {
54 Self {
55 suspension_stiffness: 5.88,
56 suspension_compression: 0.83,
57 suspension_damping: 0.88,
58 max_suspension_travel: 5.0,
59 side_friction_stiffness: 1.0,
60 friction_slip: 10.5,
61 max_suspension_force: 6000.0,
62 }
63 }
64}
65
66struct WheelDesc {
68 pub chassis_connection_cs: Point<Real>,
70 pub direction_cs: Vector<Real>,
74 pub axle_cs: Vector<Real>,
76 pub suspension_rest_length: Real,
78 pub max_suspension_travel: Real,
80 pub radius: Real,
82
83 pub suspension_stiffness: Real,
87 pub damping_compression: Real,
89 pub damping_relaxation: Real,
93 pub friction_slip: Real,
98 pub max_suspension_force: Real,
100 pub side_friction_stiffness: Real,
102}
103
104#[derive(Copy, Clone, Debug, PartialEq)]
105pub struct Wheel {
107 raycast_info: RayCastInfo,
108
109 center: Point<Real>,
110 wheel_direction_ws: Vector<Real>,
111 wheel_axle_ws: Vector<Real>,
112
113 pub chassis_connection_point_cs: Point<Real>,
115 pub direction_cs: Vector<Real>,
119 pub axle_cs: Vector<Real>,
121 pub suspension_rest_length: Real,
123 pub max_suspension_travel: Real,
125 pub radius: Real,
127 pub suspension_stiffness: Real,
131 pub damping_compression: Real,
133 pub damping_relaxation: Real,
137 pub friction_slip: Real,
142 pub side_friction_stiffness: Real,
144 pub rotation: Real,
146 delta_rotation: Real,
147 roll_influence: Real, pub max_suspension_force: Real,
150
151 pub forward_impulse: Real,
153 pub side_impulse: Real,
155
156 pub steering: Real,
158 pub engine_force: Real,
160 pub brake: Real,
162
163 clipped_inv_contact_dot_suspension: Real,
164 suspension_relative_velocity: Real,
165 pub wheel_suspension_force: Real,
167 skid_info: Real,
168}
169
170impl Wheel {
171 fn new(info: WheelDesc) -> Self {
172 Self {
173 raycast_info: RayCastInfo::default(),
174 suspension_rest_length: info.suspension_rest_length,
175 max_suspension_travel: info.max_suspension_travel,
176 radius: info.radius,
177 suspension_stiffness: info.suspension_stiffness,
178 damping_compression: info.damping_compression,
179 damping_relaxation: info.damping_relaxation,
180 chassis_connection_point_cs: info.chassis_connection_cs,
181 direction_cs: info.direction_cs,
182 axle_cs: info.axle_cs,
183 wheel_direction_ws: info.direction_cs,
184 wheel_axle_ws: info.axle_cs,
185 center: Point::origin(),
186 friction_slip: info.friction_slip,
187 steering: 0.0,
188 engine_force: 0.0,
189 rotation: 0.0,
190 delta_rotation: 0.0,
191 brake: 0.0,
192 roll_influence: 0.1,
193 clipped_inv_contact_dot_suspension: 0.0,
194 suspension_relative_velocity: 0.0,
195 wheel_suspension_force: 0.0,
196 max_suspension_force: info.max_suspension_force,
197 skid_info: 0.0,
198 side_impulse: 0.0,
199 forward_impulse: 0.0,
200 side_friction_stiffness: info.side_friction_stiffness,
201 }
202 }
203
204 pub fn raycast_info(&self) -> &RayCastInfo {
207 &self.raycast_info
208 }
209
210 pub fn center(&self) -> Point<Real> {
212 self.center
213 }
214
215 pub fn suspension(&self) -> Vector<Real> {
217 self.wheel_direction_ws
218 }
219
220 pub fn axle(&self) -> Vector<Real> {
222 self.wheel_axle_ws
223 }
224}
225
226#[derive(Copy, Clone, Debug, PartialEq, Default)]
229pub struct RayCastInfo {
230 pub contact_normal_ws: Vector<Real>,
232 pub contact_point_ws: Point<Real>,
234 pub suspension_length: Real,
236 pub hard_point_ws: Point<Real>,
238 pub is_in_contact: bool,
240 pub ground_object: Option<ColliderHandle>,
242}
243
244impl DynamicRayCastVehicleController {
245 pub fn new(chassis: RigidBodyHandle) -> Self {
249 Self {
250 wheels: vec![],
251 forward_ws: vec![],
252 axle: vec![],
253 current_vehicle_speed: 0.0,
254 chassis,
255 index_up_axis: 1,
256 index_forward_axis: 0,
257 }
258 }
259
260 pub fn add_wheel(
265 &mut self,
266 chassis_connection_cs: Point<Real>,
267 direction_cs: Vector<Real>,
268 axle_cs: Vector<Real>,
269 suspension_rest_length: Real,
270 radius: Real,
271 tuning: &WheelTuning,
272 ) -> &mut Wheel {
273 let ci = WheelDesc {
274 chassis_connection_cs,
275 direction_cs,
276 axle_cs,
277 suspension_rest_length,
278 radius,
279 suspension_stiffness: tuning.suspension_stiffness,
280 damping_compression: tuning.suspension_compression,
281 damping_relaxation: tuning.suspension_damping,
282 friction_slip: tuning.friction_slip,
283 max_suspension_travel: tuning.max_suspension_travel,
284 max_suspension_force: tuning.max_suspension_force,
285 side_friction_stiffness: tuning.side_friction_stiffness,
286 };
287
288 let wheel_id = self.wheels.len();
289 self.wheels.push(Wheel::new(ci));
290
291 &mut self.wheels[wheel_id]
292 }
293
294 #[cfg(feature = "dim2")]
295 fn update_wheel_transform(&mut self, chassis: &RigidBody, wheel_index: usize) {
296 self.update_wheel_transforms_ws(chassis, wheel_index);
297 let wheel = &mut self.wheels[wheel_index];
298 wheel.center = (wheel.raycast_info.hard_point_ws
299 + wheel.wheel_direction_ws * wheel.raycast_info.suspension_length)
300 .coords;
301 }
302
303 #[cfg(feature = "dim3")]
304 fn update_wheel_transform(&mut self, chassis: &RigidBody, wheel_index: usize) {
305 self.update_wheel_transforms_ws(chassis, wheel_index);
306 let wheel = &mut self.wheels[wheel_index];
307
308 let steering_orn = Rotation::new(-wheel.wheel_direction_ws * wheel.steering);
309 wheel.wheel_axle_ws = steering_orn * (chassis.position() * wheel.axle_cs);
310 wheel.center = wheel.raycast_info.hard_point_ws
311 + wheel.wheel_direction_ws * wheel.raycast_info.suspension_length;
312 }
313
314 fn update_wheel_transforms_ws(&mut self, chassis: &RigidBody, wheel_id: usize) {
315 let wheel = &mut self.wheels[wheel_id];
316 wheel.raycast_info.is_in_contact = false;
317
318 let chassis_transform = chassis.position();
319
320 wheel.raycast_info.hard_point_ws = chassis_transform * wheel.chassis_connection_point_cs;
321 wheel.wheel_direction_ws = chassis_transform * wheel.direction_cs;
322 wheel.wheel_axle_ws = chassis_transform * wheel.axle_cs;
323 }
324
325 #[profiling::function]
326 fn ray_cast(&mut self, queries: &QueryPipeline, chassis: &RigidBody, wheel_id: usize) {
327 let wheel = &mut self.wheels[wheel_id];
328 let raylen = wheel.suspension_rest_length + wheel.radius;
329 let rayvector = wheel.wheel_direction_ws * raylen;
330 let source = wheel.raycast_info.hard_point_ws;
331 wheel.raycast_info.contact_point_ws = source + rayvector;
332 let ray = Ray::new(source, rayvector);
333 let hit = queries.cast_ray_and_get_normal(&ray, 1.0, true);
334
335 wheel.raycast_info.ground_object = None;
336
337 if let Some((collider_hit, mut hit)) = hit {
338 if hit.time_of_impact == 0.0 {
339 let collider = &queries.colliders[collider_hit];
340 let up_ray = Ray::new(source + rayvector, -rayvector);
341 if let Some(hit2) =
342 collider
343 .shape
344 .cast_ray_and_get_normal(collider.position(), &up_ray, 1.0, false)
345 {
346 hit.normal = -hit2.normal;
347 }
348
349 if hit.normal == Vector::zeros() {
350 hit.normal = -wheel.wheel_direction_ws;
352 }
353 }
354
355 wheel.raycast_info.contact_normal_ws = hit.normal;
356 wheel.raycast_info.is_in_contact = true;
357 wheel.raycast_info.ground_object = Some(collider_hit);
358
359 let hit_distance = hit.time_of_impact * raylen;
360 wheel.raycast_info.suspension_length = hit_distance - wheel.radius;
361
362 let min_suspension_length = wheel.suspension_rest_length - wheel.max_suspension_travel;
364 let max_suspension_length = wheel.suspension_rest_length + wheel.max_suspension_travel;
365 wheel.raycast_info.suspension_length = wheel
366 .raycast_info
367 .suspension_length
368 .clamp(min_suspension_length, max_suspension_length);
369 wheel.raycast_info.contact_point_ws = ray.point_at(hit.time_of_impact);
370
371 let denominator = wheel
372 .raycast_info
373 .contact_normal_ws
374 .dot(&wheel.wheel_direction_ws);
375 let chassis_velocity_at_contact_point =
376 chassis.velocity_at_point(&wheel.raycast_info.contact_point_ws);
377 let proj_vel = wheel
378 .raycast_info
379 .contact_normal_ws
380 .dot(&chassis_velocity_at_contact_point);
381
382 if denominator >= -0.1 {
383 wheel.suspension_relative_velocity = 0.0;
384 wheel.clipped_inv_contact_dot_suspension = 1.0 / 0.1;
385 } else {
386 let inv = -1.0 / denominator;
387 wheel.suspension_relative_velocity = proj_vel * inv;
388 wheel.clipped_inv_contact_dot_suspension = inv;
389 }
390 } else {
391 wheel.raycast_info.suspension_length = wheel.suspension_rest_length;
393 wheel.suspension_relative_velocity = 0.0;
394 wheel.raycast_info.contact_normal_ws = -wheel.wheel_direction_ws;
395 wheel.clipped_inv_contact_dot_suspension = 1.0;
396 }
397 }
398
399 #[profiling::function]
401 pub fn update_vehicle(&mut self, dt: Real, queries: QueryPipelineMut) {
402 let num_wheels = self.wheels.len();
403 let chassis = &queries.bodies[self.chassis];
404
405 for i in 0..num_wheels {
406 self.update_wheel_transform(chassis, i);
407 }
408
409 self.current_vehicle_speed = chassis.linvel().norm();
410
411 let forward_w = chassis.position() * Vector::ith(self.index_forward_axis, 1.0);
412
413 if forward_w.dot(chassis.linvel()) < 0.0 {
414 self.current_vehicle_speed *= -1.0;
415 }
416
417 for wheel_id in 0..self.wheels.len() {
422 self.ray_cast(&queries.as_ref(), chassis, wheel_id);
423 }
424
425 let chassis_mass = chassis.mass();
426 self.update_suspension(chassis_mass);
427
428 let chassis = queries
429 .bodies
430 .get_mut_internal_with_modification_tracking(self.chassis)
431 .unwrap();
432
433 for wheel in &mut self.wheels {
434 if wheel.engine_force > 0.0 {
435 chassis.wake_up(true);
436 }
437
438 let mut suspension_force = wheel.wheel_suspension_force;
440
441 if suspension_force > wheel.max_suspension_force {
442 suspension_force = wheel.max_suspension_force;
443 }
444
445 let impulse = wheel.raycast_info.contact_normal_ws * suspension_force * dt;
446 chassis.apply_impulse_at_point(impulse, wheel.raycast_info.contact_point_ws, false);
447 }
448
449 self.update_friction(queries.bodies, queries.colliders, dt);
450
451 let chassis = queries
452 .bodies
453 .get_mut_internal_with_modification_tracking(self.chassis)
454 .unwrap();
455
456 for wheel in &mut self.wheels {
457 let vel = chassis.velocity_at_point(&wheel.raycast_info.hard_point_ws);
458
459 if wheel.raycast_info.is_in_contact {
460 let mut fwd = chassis.position() * Vector::ith(self.index_forward_axis, 1.0);
461 let proj = fwd.dot(&wheel.raycast_info.contact_normal_ws);
462 fwd -= wheel.raycast_info.contact_normal_ws * proj;
463
464 let proj2 = fwd.dot(&vel);
465
466 wheel.delta_rotation = (proj2 * dt) / (wheel.radius);
467 wheel.rotation += wheel.delta_rotation;
468 } else {
469 wheel.rotation += wheel.delta_rotation;
470 }
471
472 wheel.delta_rotation *= 0.99; }
474 }
475
476 pub fn wheels(&self) -> &[Wheel] {
478 &self.wheels
479 }
480
481 pub fn wheels_mut(&mut self) -> &mut [Wheel] {
483 &mut self.wheels
484 }
485
486 fn update_suspension(&mut self, chassis_mass: Real) {
487 for w_it in 0..self.wheels.len() {
488 let wheels = &mut self.wheels[w_it];
489
490 if wheels.raycast_info.is_in_contact {
491 let mut force;
492 {
494 let rest_length = wheels.suspension_rest_length;
495 let current_length = wheels.raycast_info.suspension_length;
496 let length_diff = rest_length - current_length;
497
498 force = wheels.suspension_stiffness
499 * length_diff
500 * wheels.clipped_inv_contact_dot_suspension;
501 }
502
503 {
505 let projected_rel_vel = wheels.suspension_relative_velocity;
506 {
507 let susp_damping = if projected_rel_vel < 0.0 {
508 wheels.damping_compression
509 } else {
510 wheels.damping_relaxation
511 };
512 force -= susp_damping * projected_rel_vel;
513 }
514 }
515
516 wheels.wheel_suspension_force = (force * chassis_mass).max(0.0);
518 } else {
519 wheels.wheel_suspension_force = 0.0;
520 }
521 }
522 }
523
524 #[profiling::function]
525 fn update_friction(&mut self, bodies: &mut RigidBodySet, colliders: &ColliderSet, dt: Real) {
526 let num_wheels = self.wheels.len();
527
528 if num_wheels == 0 {
529 return;
530 }
531
532 self.forward_ws.resize(num_wheels, Default::default());
533 self.axle.resize(num_wheels, Default::default());
534
535 let mut num_wheels_on_ground = 0;
536
537 for wheel in &mut self.wheels {
539 let ground_object = wheel.raycast_info.ground_object;
540
541 if ground_object.is_some() {
542 num_wheels_on_ground += 1;
543 }
544
545 wheel.side_impulse = 0.0;
546 wheel.forward_impulse = 0.0;
547 }
548
549 {
550 for i in 0..num_wheels {
551 let wheel = &mut self.wheels[i];
552 let ground_object = wheel.raycast_info.ground_object;
553
554 if ground_object.is_some() {
555 self.axle[i] = wheel.wheel_axle_ws;
556
557 let surf_normal_ws = wheel.raycast_info.contact_normal_ws;
558 let proj = self.axle[i].dot(&surf_normal_ws);
559 self.axle[i] -= surf_normal_ws * proj;
560 self.axle[i] = self.axle[i]
561 .try_normalize(1.0e-5)
562 .unwrap_or_else(Vector::zeros);
563 self.forward_ws[i] = surf_normal_ws
564 .cross(&self.axle[i])
565 .try_normalize(1.0e-5)
566 .unwrap_or_else(Vector::zeros);
567
568 if let Some(ground_body) = ground_object
569 .and_then(|h| colliders[h].parent())
570 .map(|h| &bodies[h])
571 .filter(|b| b.is_dynamic())
572 {
573 wheel.side_impulse = resolve_single_bilateral(
574 &bodies[self.chassis],
575 &wheel.raycast_info.contact_point_ws,
576 ground_body,
577 &wheel.raycast_info.contact_point_ws,
578 &self.axle[i],
579 );
580 } else {
581 wheel.side_impulse = resolve_single_unilateral(
582 &bodies[self.chassis],
583 &wheel.raycast_info.contact_point_ws,
584 &self.axle[i],
585 );
586 }
587
588 wheel.side_impulse *= wheel.side_friction_stiffness;
589 }
590 }
591 }
592
593 let side_factor = 1.0;
594 let fwd_factor = 0.5;
595
596 let mut sliding = false;
597 {
598 for wheel_id in 0..num_wheels {
599 let wheel = &mut self.wheels[wheel_id];
600 let ground_object = wheel.raycast_info.ground_object;
601
602 let mut rolling_friction = 0.0;
603
604 if ground_object.is_some() {
605 if wheel.engine_force != 0.0 {
606 rolling_friction = wheel.engine_force * dt;
607 } else {
608 let default_rolling_friction_impulse = 0.0;
609 let max_impulse = if wheel.brake != 0.0 {
610 wheel.brake
611 } else {
612 default_rolling_friction_impulse
613 };
614 let contact_pt = WheelContactPoint::new(
615 &bodies[self.chassis],
616 ground_object
617 .and_then(|h| colliders[h].parent())
618 .map(|h| &bodies[h]),
619 wheel.raycast_info.contact_point_ws,
620 self.forward_ws[wheel_id],
621 max_impulse,
622 );
623 assert!(num_wheels_on_ground > 0);
624 rolling_friction = contact_pt.calc_rolling_friction(num_wheels_on_ground);
625 }
626 }
627
628 wheel.forward_impulse = 0.0;
631 wheel.skid_info = 1.0;
632
633 if ground_object.is_some() {
634 let max_imp = wheel.wheel_suspension_force * dt * wheel.friction_slip;
635 let max_imp_side = max_imp;
636 let max_imp_squared = max_imp * max_imp_side;
637 assert!(max_imp_squared >= 0.0);
638
639 wheel.forward_impulse = rolling_friction;
640
641 let x = wheel.forward_impulse * fwd_factor;
642 let y = wheel.side_impulse * side_factor;
643
644 let impulse_squared = x * x + y * y;
645
646 if impulse_squared > max_imp_squared {
647 sliding = true;
648
649 let factor = max_imp * crate::utils::inv(impulse_squared.sqrt());
650 wheel.skid_info *= factor;
651 }
652 }
653 }
654 }
655
656 if sliding {
657 for wheel in &mut self.wheels {
658 if wheel.side_impulse != 0.0 && wheel.skid_info < 1.0 {
659 wheel.forward_impulse *= wheel.skid_info;
660 wheel.side_impulse *= wheel.skid_info;
661 }
662 }
663 }
664
665 {
667 let chassis = bodies
668 .get_mut_internal_with_modification_tracking(self.chassis)
669 .unwrap();
670
671 for wheel_id in 0..num_wheels {
672 let wheel = &self.wheels[wheel_id];
673 let mut impulse_point = wheel.raycast_info.contact_point_ws;
674
675 if wheel.forward_impulse != 0.0 {
676 chassis.apply_impulse_at_point(
677 self.forward_ws[wheel_id] * wheel.forward_impulse,
678 impulse_point,
679 false,
680 );
681 }
682 if wheel.side_impulse != 0.0 {
683 let side_impulse = self.axle[wheel_id] * wheel.side_impulse;
684
685 let v_chassis_world_up =
686 chassis.position().rotation * Vector::ith(self.index_up_axis, 1.0);
687 impulse_point -= v_chassis_world_up
688 * (v_chassis_world_up.dot(&(impulse_point - chassis.center_of_mass()))
689 * (1.0 - wheel.roll_influence));
690
691 chassis.apply_impulse_at_point(side_impulse, impulse_point, false);
692
693 }
701 }
702 }
703 }
704}
705
706struct WheelContactPoint<'a> {
707 body0: &'a RigidBody,
708 body1: Option<&'a RigidBody>,
709 friction_position_world: Point<Real>,
710 friction_direction_world: Vector<Real>,
711 jac_diag_ab_inv: Real,
712 max_impulse: Real,
713}
714
715impl<'a> WheelContactPoint<'a> {
716 pub fn new(
717 body0: &'a RigidBody,
718 body1: Option<&'a RigidBody>,
719 friction_position_world: Point<Real>,
720 friction_direction_world: Vector<Real>,
721 max_impulse: Real,
722 ) -> Self {
723 fn impulse_denominator(body: &RigidBody, pos: &Point<Real>, n: &Vector<Real>) -> Real {
724 let dpt = pos - body.center_of_mass();
725 let gcross = dpt.gcross(*n);
726 let v = (body.mprops.effective_world_inv_inertia_sqrt
727 * (body.mprops.effective_world_inv_inertia_sqrt * gcross))
728 .gcross(dpt);
729 body.mprops.local_mprops.inv_mass + n.dot(&v)
731 }
732 let denom0 =
733 impulse_denominator(body0, &friction_position_world, &friction_direction_world);
734 let denom1 = body1
735 .map(|body1| {
736 impulse_denominator(body1, &friction_position_world, &friction_direction_world)
737 })
738 .unwrap_or(0.0);
739 let relaxation = 1.0;
740 let jac_diag_ab_inv = relaxation / (denom0 + denom1);
741
742 Self {
743 body0,
744 body1,
745 friction_position_world,
746 friction_direction_world,
747 jac_diag_ab_inv,
748 max_impulse,
749 }
750 }
751
752 pub fn calc_rolling_friction(&self, num_wheels_on_ground: usize) -> Real {
753 let contact_pos_world = self.friction_position_world;
754 let max_impulse = self.max_impulse;
755
756 let vel1 = self.body0.velocity_at_point(&contact_pos_world);
757 let vel2 = self
758 .body1
759 .map(|b| b.velocity_at_point(&contact_pos_world))
760 .unwrap_or_else(Vector::zeros);
761 let vel = vel1 - vel2;
762 let vrel = self.friction_direction_world.dot(&vel);
763
764 (-vrel * self.jac_diag_ab_inv / (num_wheels_on_ground as Real))
766 .clamp(-max_impulse, max_impulse)
767 }
768}
769
770fn resolve_single_bilateral(
771 body1: &RigidBody,
772 pt1: &Point<Real>,
773 body2: &RigidBody,
774 pt2: &Point<Real>,
775 normal: &Vector<Real>,
776) -> Real {
777 let vel1 = body1.velocity_at_point(pt1);
778 let vel2 = body2.velocity_at_point(pt2);
779 let dvel = vel1 - vel2;
780
781 let dpt1 = pt1 - body1.center_of_mass();
782 let dpt2 = pt2 - body2.center_of_mass();
783 let aj = dpt1.gcross(*normal);
784 let bj = dpt2.gcross(-*normal);
785 let iaj = body1.mprops.effective_world_inv_inertia_sqrt * aj;
786 let ibj = body2.mprops.effective_world_inv_inertia_sqrt * bj;
787
788 let im1 = body1.mprops.local_mprops.inv_mass;
790 let im2 = body2.mprops.local_mprops.inv_mass;
791
792 let jac_diag_ab = im1 + im2 + iaj.gdot(iaj) + ibj.gdot(ibj);
793 let jac_diag_ab_inv = crate::utils::inv(jac_diag_ab);
794 let rel_vel = normal.dot(&dvel);
795
796 let contact_damping = 0.2;
798 -contact_damping * rel_vel * jac_diag_ab_inv
799}
800
801fn resolve_single_unilateral(body1: &RigidBody, pt1: &Point<Real>, normal: &Vector<Real>) -> Real {
802 let vel1 = body1.velocity_at_point(pt1);
803 let dvel = vel1;
804 let dpt1 = pt1 - body1.center_of_mass();
805 let aj = dpt1.gcross(*normal);
806 let iaj = body1.mprops.effective_world_inv_inertia_sqrt * aj;
807
808 let im1 = body1.mprops.local_mprops.inv_mass;
810 let jac_diag_ab = im1 + iaj.gdot(iaj);
811 let jac_diag_ab_inv = crate::utils::inv(jac_diag_ab);
812 let rel_vel = normal.dot(&dvel);
813
814 let contact_damping = 0.2;
816 -contact_damping * rel_vel * jac_diag_ab_inv
817}