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