rapier3d/control/
ray_cast_vehicle_controller.rs

1//! A vehicle controller based on ray-casting, ported and modified from Bullet’s `btRaycastVehicle`.
2
3use 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/// A character controller to simulate vehicles using ray-casting for the wheels.
11#[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    /// The current forward speed of the vehicle.
18    pub current_vehicle_speed: Real,
19
20    /// Handle of the vehicle’s chassis.
21    pub chassis: RigidBodyHandle,
22    /// The chassis’ local _up_ direction (`0 = x, 1 = y, 2 = z`)
23    pub index_up_axis: usize,
24    /// The chassis’ local _forward_ direction (`0 = x, 1 = y, 2 = z`)
25    pub index_forward_axis: usize,
26}
27
28#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
29#[derive(Copy, Clone, Debug, PartialEq)]
30/// Parameters affecting the physical behavior of a wheel.
31pub struct WheelTuning {
32    /// The suspension stiffness.
33    ///
34    /// Increase this value if the suspension appears to not push the vehicle strong enough.
35    pub suspension_stiffness: Real,
36    /// The suspension’s damping when it is being compressed.
37    pub suspension_compression: Real,
38    /// The suspension’s damping when it is being released.
39    ///
40    /// Increase this value if the suspension appears to overshoot.
41    pub suspension_damping: Real,
42    /// The maximum distance the suspension can travel before and after its resting length.
43    pub max_suspension_travel: Real,
44    /// The multiplier of friction between a tire and the collider it's on top of.
45    pub side_friction_stiffness: Real,
46    /// Parameter controlling how much traction the tire has.
47    ///
48    /// The larger the value, the more instantaneous braking will happen (with the risk of
49    /// causing the vehicle to flip if it’s too strong).
50    pub friction_slip: Real,
51    /// The maximum force applied by the suspension.
52    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
69/// Objects used to initialize a wheel.
70struct WheelDesc {
71    /// The position of the wheel, relative to the chassis.
72    pub chassis_connection_cs: Point<Real>,
73    /// The direction of the wheel’s suspension, relative to the chassis.
74    ///
75    /// The ray-casting will happen following this direction to detect the ground.
76    pub direction_cs: Vector<Real>,
77    /// The wheel’s axle axis, relative to the chassis.
78    pub axle_cs: Vector<Real>,
79    /// The rest length of the wheel’s suspension spring.
80    pub suspension_rest_length: Real,
81    /// The maximum distance the suspension can travel before and after its resting length.
82    pub max_suspension_travel: Real,
83    /// The wheel’s radius.
84    pub radius: Real,
85
86    /// The suspension stiffness.
87    ///
88    /// Increase this value if the suspension appears to not push the vehicle strong enough.
89    pub suspension_stiffness: Real,
90    /// The suspension’s damping when it is being compressed.
91    pub damping_compression: Real,
92    /// The suspension’s damping when it is being released.
93    ///
94    /// Increase this value if the suspension appears to overshoot.
95    pub damping_relaxation: Real,
96    /// Parameter controlling how much traction the tire has.
97    ///
98    /// The larger the value, the more instantaneous braking will happen (with the risk of
99    /// causing the vehicle to flip if it’s too strong).
100    pub friction_slip: Real,
101    /// The maximum force applied by the suspension.
102    pub max_suspension_force: Real,
103    /// The multiplier of friction between a tire and the collider it's on top of.
104    pub side_friction_stiffness: Real,
105}
106
107#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
108#[derive(Copy, Clone, Debug, PartialEq)]
109/// A wheel attached to a vehicle.
110pub 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    /// The position of the wheel, relative to the chassis.
118    pub chassis_connection_point_cs: Point<Real>,
119    /// The direction of the wheel’s suspension, relative to the chassis.
120    ///
121    /// The ray-casting will happen following this direction to detect the ground.
122    pub direction_cs: Vector<Real>,
123    /// The wheel’s axle axis, relative to the chassis.
124    pub axle_cs: Vector<Real>,
125    /// The rest length of the wheel’s suspension spring.
126    pub suspension_rest_length: Real,
127    /// The maximum distance the suspension can travel before and after its resting length.
128    pub max_suspension_travel: Real,
129    /// The wheel’s radius.
130    pub radius: Real,
131    /// The suspension stiffness.
132    ///
133    /// Increase this value if the suspension appears to not push the vehicle strong enough.
134    pub suspension_stiffness: Real,
135    /// The suspension’s damping when it is being compressed.
136    pub damping_compression: Real,
137    /// The suspension’s damping when it is being released.
138    ///
139    /// Increase this value if the suspension appears to overshoot.
140    pub damping_relaxation: Real,
141    /// Parameter controlling how much traction the tire has.
142    ///
143    /// The larger the value, the more instantaneous braking will happen (with the risk of
144    /// causing the vehicle to flip if it’s too strong).
145    pub friction_slip: Real,
146    /// The multiplier of friction between a tire and the collider it's on top of.
147    pub side_friction_stiffness: Real,
148    /// The wheel’s current rotation on its axle.
149    pub rotation: Real,
150    delta_rotation: Real,
151    roll_influence: Real, // TODO: make this public?
152    /// The maximum force applied by the suspension.
153    pub max_suspension_force: Real,
154
155    /// The forward impulses applied by the wheel on the chassis.
156    pub forward_impulse: Real,
157    /// The side impulses applied by the wheel on the chassis.
158    pub side_impulse: Real,
159
160    /// The steering angle for this wheel.
161    pub steering: Real,
162    /// The forward force applied by this wheel on the chassis.
163    pub engine_force: Real,
164    /// The maximum amount of braking impulse applied to slow down the vehicle.
165    pub brake: Real,
166
167    clipped_inv_contact_dot_suspension: Real,
168    suspension_relative_velocity: Real,
169    /// The force applied by the suspension.
170    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    /// Information about suspension and the ground obtained from the ray-casting
209    /// for this wheel.
210    pub fn raycast_info(&self) -> &RayCastInfo {
211        &self.raycast_info
212    }
213
214    /// The world-space center of the wheel.
215    pub fn center(&self) -> Point<Real> {
216        self.center
217    }
218
219    /// The world-space direction of the wheel’s suspension.
220    pub fn suspension(&self) -> Vector<Real> {
221        self.wheel_direction_ws
222    }
223
224    /// The world-space direction of the wheel’s axle.
225    pub fn axle(&self) -> Vector<Real> {
226        self.wheel_axle_ws
227    }
228}
229
230/// Information about suspension and the ground obtained from the ray-casting
231/// to simulate a wheel’s suspension.
232#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
233#[derive(Copy, Clone, Debug, PartialEq, Default)]
234pub struct RayCastInfo {
235    /// The (world-space) contact normal between the wheel and the floor.
236    pub contact_normal_ws: Vector<Real>,
237    /// The (world-space) point hit by the wheel’s ray-cast.
238    pub contact_point_ws: Point<Real>,
239    /// The suspension length for the wheel.
240    pub suspension_length: Real,
241    /// The (world-space) starting point of the ray-cast.
242    pub hard_point_ws: Point<Real>,
243    /// Is the wheel in contact with the ground?
244    pub is_in_contact: bool,
245    /// The collider hit by the ray-cast.
246    pub ground_object: Option<ColliderHandle>,
247}
248
249impl DynamicRayCastVehicleController {
250    /// Creates a new vehicle represented by the given rigid-body.
251    ///
252    /// Wheels have to be attached afterwards calling [`Self::add_wheel`].
253    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    //
266    // basically most of the code is general for 2 or 4 wheel vehicles, but some of it needs to be reviewed
267    //
268    /// Adds a wheel to this vehicle.
269    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                    // If the hit is still not defined, set the normal.
356                    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            // clamp on max suspension travel
368            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            // No contact, put wheel info as in rest position
397            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    /// Updates the vehicle’s velocity based on its suspension, engine force, and brake.
405    #[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        //
423        // simulate suspension
424        //
425
426        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            // apply suspension force
444            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; //damping of rotation when not in contact
478        }
479    }
480
481    /// Reference to all the wheels attached to this vehicle.
482    pub fn wheels(&self) -> &[Wheel] {
483        &self.wheels
484    }
485
486    /// Mutable reference to all the wheels attached to this vehicle.
487    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                //	Spring
498                {
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                // Damper
509                {
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                // RESULT
522                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        //TODO: collapse all those loops into one!
543        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                //switch between active rolling (throttle), braking and non-active rolling friction (no throttle/break)
634
635                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        // apply the impulses
671        {
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                    // TODO: apply friction impulse on the ground
699                    // let ground_object = self.wheels[wheel_id].raycast_info.ground_object;
700                    // ground_object.apply_impulse_at_point(
701                    //     -side_impulse,
702                    //     wheels.raycast_info.contact_point_ws,
703                    //     false,
704                    // );
705                }
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            // TODO: take the effective inv mass into account instead of the inv_mass?
733            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        // calculate friction that moves us to zero relative velocity
768        (-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    // TODO: take the effective_inv_mass into account?
792    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    //todo: move this into proper structure
800    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    // TODO: take the effective_inv_mass into account?
812    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    //todo: move this into proper structure
818    let contact_damping = 0.2;
819    -contact_damping * rel_vel * jac_diag_ab_inv
820}