Skip to main content

rapier3d/control/
ray_cast_vehicle_controller.rs

1//! A vehicle controller based on ray-casting, ported and modified from Bullet’s `btRaycastVehicle`.
2use crate::alloc_prelude::*;
3
4#[cfg(not(feature = "std"))]
5use simba::scalar::ComplexField;
6
7use crate::dynamics::{RigidBody, RigidBodyHandle, RigidBodySet};
8use crate::geometry::{ColliderHandle, ColliderSet, Ray};
9use crate::math::{Real, Vector, VectorExt, rotation_from_angle};
10use crate::pipeline::QueryPipeline;
11use crate::prelude::QueryPipelineMut;
12use crate::utils::{CrossProduct, DotProduct};
13
14/// A character controller to simulate vehicles using ray-casting for the wheels.
15#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
16#[derive(Clone, Debug)]
17pub struct DynamicRayCastVehicleController {
18    wheels: Vec<Wheel>,
19    forward_ws: Vec<Vector>,
20    axle: Vec<Vector>,
21    /// The current forward speed of the vehicle.
22    pub current_vehicle_speed: Real,
23
24    /// Handle of the vehicle’s chassis.
25    pub chassis: RigidBodyHandle,
26    /// The chassis’ local _up_ direction (`0 = x, 1 = y, 2 = z`)
27    pub index_up_axis: usize,
28    /// The chassis’ local _forward_ direction (`0 = x, 1 = y, 2 = z`)
29    pub index_forward_axis: usize,
30}
31
32#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
33#[derive(Copy, Clone, Debug, PartialEq)]
34/// Parameters affecting the physical behavior of a wheel.
35pub struct WheelTuning {
36    /// The suspension stiffness.
37    ///
38    /// Increase this value if the suspension appears to not push the vehicle strong enough.
39    pub suspension_stiffness: Real,
40    /// The suspension’s damping when it is being compressed.
41    pub suspension_compression: Real,
42    /// The suspension’s damping when it is being released.
43    ///
44    /// Increase this value if the suspension appears to overshoot.
45    pub suspension_damping: Real,
46    /// The maximum distance the suspension can travel before and after its resting length.
47    pub max_suspension_travel: Real,
48    /// The multiplier of friction between a tire and the collider it's on top of.
49    pub side_friction_stiffness: Real,
50    /// Parameter controlling how much traction the tire has.
51    ///
52    /// The larger the value, the more instantaneous braking will happen (with the risk of
53    /// causing the vehicle to flip if it’s too strong).
54    pub friction_slip: Real,
55    /// The maximum force applied by the suspension.
56    pub max_suspension_force: Real,
57}
58
59impl Default for WheelTuning {
60    fn default() -> Self {
61        Self {
62            suspension_stiffness: 5.88,
63            suspension_compression: 0.83,
64            suspension_damping: 0.88,
65            max_suspension_travel: 5.0,
66            side_friction_stiffness: 1.0,
67            friction_slip: 10.5,
68            max_suspension_force: 6000.0,
69        }
70    }
71}
72
73/// Objects used to initialize a wheel.
74struct WheelDesc {
75    /// The position of the wheel, relative to the chassis.
76    pub chassis_connection_cs: Vector,
77    /// The direction of the wheel’s suspension, relative to the chassis.
78    ///
79    /// The ray-casting will happen following this direction to detect the ground.
80    pub direction_cs: Vector,
81    /// The wheel’s axle axis, relative to the chassis.
82    pub axle_cs: Vector,
83    /// The rest length of the wheel’s suspension spring.
84    pub suspension_rest_length: Real,
85    /// The maximum distance the suspension can travel before and after its resting length.
86    pub max_suspension_travel: Real,
87    /// The wheel’s radius.
88    pub radius: Real,
89
90    /// The suspension stiffness.
91    ///
92    /// Increase this value if the suspension appears to not push the vehicle strong enough.
93    pub suspension_stiffness: Real,
94    /// The suspension’s damping when it is being compressed.
95    pub damping_compression: Real,
96    /// The suspension’s damping when it is being released.
97    ///
98    /// Increase this value if the suspension appears to overshoot.
99    pub damping_relaxation: Real,
100    /// Parameter controlling how much traction the tire has.
101    ///
102    /// The larger the value, the more instantaneous braking will happen (with the risk of
103    /// causing the vehicle to flip if it’s too strong).
104    pub friction_slip: Real,
105    /// The maximum force applied by the suspension.
106    pub max_suspension_force: Real,
107    /// The multiplier of friction between a tire and the collider it's on top of.
108    pub side_friction_stiffness: Real,
109}
110
111#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
112#[derive(Copy, Clone, Debug, PartialEq)]
113/// A wheel attached to a vehicle.
114pub struct Wheel {
115    raycast_info: RayCastInfo,
116
117    center: Vector,
118    wheel_direction_ws: Vector,
119    wheel_axle_ws: Vector,
120
121    /// The position of the wheel, relative to the chassis.
122    pub chassis_connection_point_cs: Vector,
123    /// The direction of the wheel’s suspension, relative to the chassis.
124    ///
125    /// The ray-casting will happen following this direction to detect the ground.
126    pub direction_cs: Vector,
127    /// The wheel’s axle axis, relative to the chassis.
128    pub axle_cs: Vector,
129    /// The rest length of the wheel’s suspension spring.
130    pub suspension_rest_length: Real,
131    /// The maximum distance the suspension can travel before and after its resting length.
132    pub max_suspension_travel: Real,
133    /// The wheel’s radius.
134    pub radius: Real,
135    /// The suspension stiffness.
136    ///
137    /// Increase this value if the suspension appears to not push the vehicle strong enough.
138    pub suspension_stiffness: Real,
139    /// The suspension’s damping when it is being compressed.
140    pub damping_compression: Real,
141    /// The suspension’s damping when it is being released.
142    ///
143    /// Increase this value if the suspension appears to overshoot.
144    pub damping_relaxation: Real,
145    /// Parameter controlling how much traction the tire has.
146    ///
147    /// The larger the value, the more instantaneous braking will happen (with the risk of
148    /// causing the vehicle to flip if it’s too strong).
149    pub friction_slip: Real,
150    /// The multiplier of friction between a tire and the collider it's on top of.
151    pub side_friction_stiffness: Real,
152    /// The wheel’s current rotation on its axle.
153    pub rotation: Real,
154    delta_rotation: Real,
155    roll_influence: Real, // TODO: make this public?
156    /// The maximum force applied by the suspension.
157    pub max_suspension_force: Real,
158
159    /// The forward impulses applied by the wheel on the chassis.
160    pub forward_impulse: Real,
161    /// The side impulses applied by the wheel on the chassis.
162    pub side_impulse: Real,
163
164    /// The steering angle for this wheel.
165    pub steering: Real,
166    /// The forward force applied by this wheel on the chassis.
167    pub engine_force: Real,
168    /// The maximum amount of braking impulse applied to slow down the vehicle.
169    pub brake: Real,
170
171    clipped_inv_contact_dot_suspension: Real,
172    suspension_relative_velocity: Real,
173    /// The force applied by the suspension.
174    pub wheel_suspension_force: Real,
175    skid_info: Real,
176}
177
178impl Wheel {
179    fn new(info: WheelDesc) -> Self {
180        Self {
181            raycast_info: RayCastInfo::default(),
182            suspension_rest_length: info.suspension_rest_length,
183            max_suspension_travel: info.max_suspension_travel,
184            radius: info.radius,
185            suspension_stiffness: info.suspension_stiffness,
186            damping_compression: info.damping_compression,
187            damping_relaxation: info.damping_relaxation,
188            chassis_connection_point_cs: info.chassis_connection_cs,
189            direction_cs: info.direction_cs,
190            axle_cs: info.axle_cs,
191            wheel_direction_ws: info.direction_cs,
192            wheel_axle_ws: info.axle_cs,
193            center: Vector::ZERO,
194            friction_slip: info.friction_slip,
195            steering: 0.0,
196            engine_force: 0.0,
197            rotation: 0.0,
198            delta_rotation: 0.0,
199            brake: 0.0,
200            roll_influence: 0.1,
201            clipped_inv_contact_dot_suspension: 0.0,
202            suspension_relative_velocity: 0.0,
203            wheel_suspension_force: 0.0,
204            max_suspension_force: info.max_suspension_force,
205            skid_info: 0.0,
206            side_impulse: 0.0,
207            forward_impulse: 0.0,
208            side_friction_stiffness: info.side_friction_stiffness,
209        }
210    }
211
212    /// Information about suspension and the ground obtained from the ray-casting
213    /// for this wheel.
214    pub fn raycast_info(&self) -> &RayCastInfo {
215        &self.raycast_info
216    }
217
218    /// The world-space center of the wheel.
219    pub fn center(&self) -> Vector {
220        self.center
221    }
222
223    /// The world-space direction of the wheel’s suspension.
224    pub fn suspension(&self) -> Vector {
225        self.wheel_direction_ws
226    }
227
228    /// The world-space direction of the wheel’s axle.
229    pub fn axle(&self) -> Vector {
230        self.wheel_axle_ws
231    }
232}
233
234/// Information about suspension and the ground obtained from the ray-casting
235/// to simulate a wheel’s suspension.
236#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
237#[derive(Copy, Clone, Debug, PartialEq, Default)]
238pub struct RayCastInfo {
239    /// The (world-space) contact normal between the wheel and the floor.
240    pub contact_normal_ws: Vector,
241    /// The (world-space) point hit by the wheel’s ray-cast.
242    pub contact_point_ws: Vector,
243    /// The suspension length for the wheel.
244    pub suspension_length: Real,
245    /// The (world-space) starting point of the ray-cast.
246    pub hard_point_ws: Vector,
247    /// Is the wheel in contact with the ground?
248    pub is_in_contact: bool,
249    /// The collider hit by the ray-cast.
250    pub ground_object: Option<ColliderHandle>,
251}
252
253impl DynamicRayCastVehicleController {
254    /// Creates a new vehicle represented by the given rigid-body.
255    ///
256    /// Wheels have to be attached afterwards calling [`Self::add_wheel`].
257    pub fn new(chassis: RigidBodyHandle) -> Self {
258        Self {
259            wheels: vec![],
260            forward_ws: vec![],
261            axle: vec![],
262            current_vehicle_speed: 0.0,
263            chassis,
264            index_up_axis: 1,
265            index_forward_axis: 0,
266        }
267    }
268
269    //
270    // basically most of the code is general for 2 or 4 wheel vehicles, but some of it needs to be reviewed
271    //
272    /// Adds a wheel to this vehicle.
273    pub fn add_wheel(
274        &mut self,
275        chassis_connection_cs: Vector,
276        direction_cs: Vector,
277        axle_cs: Vector,
278        suspension_rest_length: Real,
279        radius: Real,
280        tuning: &WheelTuning,
281    ) -> &mut Wheel {
282        let ci = WheelDesc {
283            chassis_connection_cs,
284            direction_cs,
285            axle_cs,
286            suspension_rest_length,
287            radius,
288            suspension_stiffness: tuning.suspension_stiffness,
289            damping_compression: tuning.suspension_compression,
290            damping_relaxation: tuning.suspension_damping,
291            friction_slip: tuning.friction_slip,
292            max_suspension_travel: tuning.max_suspension_travel,
293            max_suspension_force: tuning.max_suspension_force,
294            side_friction_stiffness: tuning.side_friction_stiffness,
295        };
296
297        let wheel_id = self.wheels.len();
298        self.wheels.push(Wheel::new(ci));
299
300        &mut self.wheels[wheel_id]
301    }
302
303    #[cfg(feature = "dim2")]
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        wheel.center = wheel.raycast_info.hard_point_ws
308            + wheel.wheel_direction_ws * wheel.raycast_info.suspension_length;
309    }
310
311    #[cfg(feature = "dim3")]
312    fn update_wheel_transform(&mut self, chassis: &RigidBody, wheel_index: usize) {
313        self.update_wheel_transforms_ws(chassis, wheel_index);
314        let wheel = &mut self.wheels[wheel_index];
315
316        let steering_orn = rotation_from_angle(-wheel.wheel_direction_ws * wheel.steering);
317        wheel.wheel_axle_ws = steering_orn * (chassis.position().rotation * wheel.axle_cs);
318        wheel.center = wheel.raycast_info.hard_point_ws
319            + wheel.wheel_direction_ws * wheel.raycast_info.suspension_length;
320    }
321
322    fn update_wheel_transforms_ws(&mut self, chassis: &RigidBody, wheel_id: usize) {
323        let wheel = &mut self.wheels[wheel_id];
324        wheel.raycast_info.is_in_contact = false;
325
326        let chassis_transform = chassis.position();
327
328        wheel.raycast_info.hard_point_ws = chassis_transform * wheel.chassis_connection_point_cs;
329        wheel.wheel_direction_ws = chassis_transform.rotation * wheel.direction_cs;
330        wheel.wheel_axle_ws = chassis_transform.rotation * wheel.axle_cs;
331    }
332
333    #[profiling::function]
334    fn ray_cast(&mut self, queries: &QueryPipeline, chassis: &RigidBody, wheel_id: usize) {
335        let wheel = &mut self.wheels[wheel_id];
336        let raylen = wheel.suspension_rest_length + wheel.radius;
337        let rayvector = wheel.wheel_direction_ws * raylen;
338        let source = wheel.raycast_info.hard_point_ws;
339        wheel.raycast_info.contact_point_ws = source + rayvector;
340        let ray = Ray::new(source, rayvector);
341        let hit = queries.cast_ray_and_get_normal(&ray, 1.0, true);
342
343        wheel.raycast_info.ground_object = None;
344
345        if let Some((collider_hit, mut hit)) = hit {
346            if hit.time_of_impact == 0.0 {
347                let collider = &queries.colliders[collider_hit];
348                let up_ray = Ray::new(source + rayvector, -rayvector);
349                if let Some(hit2) =
350                    collider
351                        .shape
352                        .cast_ray_and_get_normal(collider.position(), &up_ray, 1.0, false)
353                {
354                    hit.normal = -hit2.normal;
355                }
356
357                if hit.normal == Vector::ZERO {
358                    // If the hit is still not defined, set the normal.
359                    hit.normal = -wheel.wheel_direction_ws;
360                }
361            }
362
363            wheel.raycast_info.contact_normal_ws = hit.normal;
364            wheel.raycast_info.is_in_contact = true;
365            wheel.raycast_info.ground_object = Some(collider_hit);
366
367            let hit_distance = hit.time_of_impact * raylen;
368            wheel.raycast_info.suspension_length = hit_distance - wheel.radius;
369
370            // clamp on max suspension travel
371            let min_suspension_length = wheel.suspension_rest_length - wheel.max_suspension_travel;
372            let max_suspension_length = wheel.suspension_rest_length + wheel.max_suspension_travel;
373            wheel.raycast_info.suspension_length = wheel
374                .raycast_info
375                .suspension_length
376                .clamp(min_suspension_length, max_suspension_length);
377            wheel.raycast_info.contact_point_ws = ray.point_at(hit.time_of_impact);
378
379            let denominator = wheel
380                .raycast_info
381                .contact_normal_ws
382                .dot(wheel.wheel_direction_ws);
383            let chassis_velocity_at_contact_point =
384                chassis.velocity_at_point(wheel.raycast_info.contact_point_ws);
385            let proj_vel = wheel
386                .raycast_info
387                .contact_normal_ws
388                .dot(chassis_velocity_at_contact_point);
389
390            if denominator >= -0.1 {
391                wheel.suspension_relative_velocity = 0.0;
392                wheel.clipped_inv_contact_dot_suspension = 1.0 / 0.1;
393            } else {
394                let inv = -1.0 / denominator;
395                wheel.suspension_relative_velocity = proj_vel * inv;
396                wheel.clipped_inv_contact_dot_suspension = inv;
397            }
398        } else {
399            // No contact, put wheel info as in rest position
400            wheel.raycast_info.suspension_length = wheel.suspension_rest_length;
401            wheel.suspension_relative_velocity = 0.0;
402            wheel.raycast_info.contact_normal_ws = -wheel.wheel_direction_ws;
403            wheel.clipped_inv_contact_dot_suspension = 1.0;
404        }
405    }
406
407    /// Updates the vehicle’s velocity based on its suspension, engine force, and brake.
408    #[profiling::function]
409    pub fn update_vehicle(&mut self, dt: Real, queries: QueryPipelineMut) {
410        let num_wheels = self.wheels.len();
411        let chassis = &queries.bodies[self.chassis];
412
413        for i in 0..num_wheels {
414            self.update_wheel_transform(chassis, i);
415        }
416
417        self.current_vehicle_speed = chassis.linvel().length();
418
419        let forward_w = chassis.position().rotation * Vector::ith(self.index_forward_axis, 1.0);
420
421        if forward_w.dot(chassis.linvel()) < 0.0 {
422            self.current_vehicle_speed *= -1.0;
423        }
424
425        //
426        // simulate suspension
427        //
428
429        for wheel_id in 0..self.wheels.len() {
430            self.ray_cast(&queries.as_ref(), chassis, wheel_id);
431        }
432
433        let chassis_mass = chassis.mass();
434        self.update_suspension(chassis_mass);
435
436        let chassis = queries
437            .bodies
438            .get_mut_internal_with_modification_tracking(self.chassis)
439            .unwrap();
440
441        for wheel in &mut self.wheels {
442            if wheel.engine_force > 0.0 {
443                chassis.wake_up(true);
444            }
445
446            // apply suspension force
447            let mut suspension_force = wheel.wheel_suspension_force;
448
449            if suspension_force > wheel.max_suspension_force {
450                suspension_force = wheel.max_suspension_force;
451            }
452
453            let impulse = wheel.raycast_info.contact_normal_ws * suspension_force * dt;
454            chassis.apply_impulse_at_point(impulse, wheel.raycast_info.contact_point_ws, false);
455        }
456
457        self.update_friction(queries.bodies, queries.colliders, dt);
458
459        let chassis = queries
460            .bodies
461            .get_mut_internal_with_modification_tracking(self.chassis)
462            .unwrap();
463
464        for wheel in &mut self.wheels {
465            let vel = chassis.velocity_at_point(wheel.raycast_info.hard_point_ws);
466
467            if wheel.raycast_info.is_in_contact {
468                // Use the same per-wheel forward direction as `update_friction` (rolling
469                // direction on the contact plane) instead of the chassis' forward axis:
470                // the latter yields a zero or sign-flipped rotation whenever it doesn't
471                // match the wheel's actual rolling direction.
472                let fwd = wheel
473                    .raycast_info
474                    .contact_normal_ws
475                    .cross(wheel.wheel_axle_ws)
476                    .normalize_or_zero();
477
478                let proj2 = fwd.dot(vel);
479
480                wheel.delta_rotation = (proj2 * dt) / (wheel.radius);
481                wheel.rotation += wheel.delta_rotation;
482            } else {
483                wheel.rotation += wheel.delta_rotation;
484            }
485
486            wheel.delta_rotation *= 0.99; //damping of rotation when not in contact
487        }
488    }
489
490    /// Reference to all the wheels attached to this vehicle.
491    pub fn wheels(&self) -> &[Wheel] {
492        &self.wheels
493    }
494
495    /// Mutable reference to all the wheels attached to this vehicle.
496    pub fn wheels_mut(&mut self) -> &mut [Wheel] {
497        &mut self.wheels
498    }
499
500    fn update_suspension(&mut self, chassis_mass: Real) {
501        for w_it in 0..self.wheels.len() {
502            let wheels = &mut self.wheels[w_it];
503
504            if wheels.raycast_info.is_in_contact {
505                let mut force;
506                //	Spring
507                {
508                    let rest_length = wheels.suspension_rest_length;
509                    let current_length = wheels.raycast_info.suspension_length;
510                    let length_diff = rest_length - current_length;
511
512                    force = wheels.suspension_stiffness
513                        * length_diff
514                        * wheels.clipped_inv_contact_dot_suspension;
515                }
516
517                // Damper
518                {
519                    let projected_rel_vel = wheels.suspension_relative_velocity;
520                    {
521                        let susp_damping = if projected_rel_vel < 0.0 {
522                            wheels.damping_compression
523                        } else {
524                            wheels.damping_relaxation
525                        };
526                        force -= susp_damping * projected_rel_vel;
527                    }
528                }
529
530                // RESULT
531                wheels.wheel_suspension_force = (force * chassis_mass).max(0.0);
532            } else {
533                wheels.wheel_suspension_force = 0.0;
534            }
535        }
536    }
537
538    #[profiling::function]
539    fn update_friction(&mut self, bodies: &mut RigidBodySet, colliders: &ColliderSet, dt: Real) {
540        let num_wheels = self.wheels.len();
541
542        if num_wheels == 0 {
543            return;
544        }
545
546        self.forward_ws.resize(num_wheels, Default::default());
547        self.axle.resize(num_wheels, Default::default());
548
549        let mut num_wheels_on_ground = 0;
550
551        //TODO: collapse all those loops into one!
552        for wheel in &mut self.wheels {
553            let ground_object = wheel.raycast_info.ground_object;
554
555            if ground_object.is_some() {
556                num_wheels_on_ground += 1;
557            }
558
559            wheel.side_impulse = 0.0;
560            wheel.forward_impulse = 0.0;
561        }
562
563        {
564            for i in 0..num_wheels {
565                let wheel = &mut self.wheels[i];
566                let ground_object = wheel.raycast_info.ground_object;
567
568                if ground_object.is_some() {
569                    self.axle[i] = wheel.wheel_axle_ws;
570
571                    let surf_normal_ws = wheel.raycast_info.contact_normal_ws;
572                    let proj = self.axle[i].dot(surf_normal_ws);
573                    self.axle[i] -= surf_normal_ws * proj;
574                    self.axle[i] = self.axle[i].normalize_or_zero();
575                    self.forward_ws[i] = surf_normal_ws.cross(self.axle[i]).normalize_or_zero();
576
577                    if let Some(ground_body) = ground_object
578                        .and_then(|h| colliders[h].parent())
579                        .map(|h| &bodies[h])
580                        .filter(|b| b.is_dynamic())
581                    {
582                        wheel.side_impulse = resolve_single_bilateral(
583                            &bodies[self.chassis],
584                            wheel.raycast_info.contact_point_ws,
585                            ground_body,
586                            wheel.raycast_info.contact_point_ws,
587                            self.axle[i],
588                        );
589                    } else {
590                        wheel.side_impulse = resolve_single_unilateral(
591                            &bodies[self.chassis],
592                            wheel.raycast_info.contact_point_ws,
593                            self.axle[i],
594                        );
595                    }
596
597                    wheel.side_impulse *= wheel.side_friction_stiffness;
598                }
599            }
600        }
601
602        let side_factor = 1.0;
603        let fwd_factor = 0.5;
604
605        let mut sliding = false;
606        {
607            for wheel_id in 0..num_wheels {
608                let wheel = &mut self.wheels[wheel_id];
609                let ground_object = wheel.raycast_info.ground_object;
610
611                let mut rolling_friction = 0.0;
612
613                if ground_object.is_some() {
614                    if wheel.engine_force != 0.0 {
615                        rolling_friction = wheel.engine_force * dt;
616                    } else {
617                        let default_rolling_friction_impulse = 0.0;
618                        let max_impulse = if wheel.brake != 0.0 {
619                            wheel.brake
620                        } else {
621                            default_rolling_friction_impulse
622                        };
623                        let contact_pt = WheelContactPoint::new(
624                            &bodies[self.chassis],
625                            ground_object
626                                .and_then(|h| colliders[h].parent())
627                                .map(|h| &bodies[h]),
628                            wheel.raycast_info.contact_point_ws,
629                            self.forward_ws[wheel_id],
630                            max_impulse,
631                        );
632                        assert!(num_wheels_on_ground > 0);
633                        rolling_friction = contact_pt.calc_rolling_friction(num_wheels_on_ground);
634                    }
635                }
636
637                //switch between active rolling (throttle), braking and non-active rolling friction (no throttle/break)
638
639                wheel.forward_impulse = 0.0;
640                wheel.skid_info = 1.0;
641
642                if ground_object.is_some() {
643                    let max_imp = wheel.wheel_suspension_force * dt * wheel.friction_slip;
644                    let max_imp_side = max_imp;
645                    let max_imp_squared = max_imp * max_imp_side;
646                    assert!(max_imp_squared >= 0.0);
647
648                    wheel.forward_impulse = rolling_friction;
649
650                    let x = wheel.forward_impulse * fwd_factor;
651                    let y = wheel.side_impulse * side_factor;
652
653                    let impulse_squared = x * x + y * y;
654
655                    if impulse_squared > max_imp_squared {
656                        sliding = true;
657
658                        let factor = max_imp * crate::utils::inv(impulse_squared.sqrt());
659                        wheel.skid_info *= factor;
660                    }
661                }
662            }
663        }
664
665        if sliding {
666            for wheel in &mut self.wheels {
667                if wheel.skid_info < 1.0 {
668                    wheel.forward_impulse *= wheel.skid_info;
669                    wheel.side_impulse *= wheel.skid_info;
670                }
671            }
672        }
673
674        // apply the impulses
675        {
676            let chassis = bodies
677                .get_mut_internal_with_modification_tracking(self.chassis)
678                .unwrap();
679
680            for wheel_id in 0..num_wheels {
681                let wheel = &self.wheels[wheel_id];
682                let mut impulse_point = wheel.raycast_info.contact_point_ws;
683
684                if wheel.forward_impulse != 0.0 {
685                    chassis.apply_impulse_at_point(
686                        self.forward_ws[wheel_id] * wheel.forward_impulse,
687                        impulse_point,
688                        false,
689                    );
690                }
691                if wheel.side_impulse != 0.0 {
692                    let side_impulse = self.axle[wheel_id] * wheel.side_impulse;
693
694                    let v_chassis_world_up =
695                        chassis.position().rotation * Vector::ith(self.index_up_axis, 1.0);
696                    impulse_point -= v_chassis_world_up
697                        * (v_chassis_world_up.dot(impulse_point - chassis.center_of_mass())
698                            * (1.0 - wheel.roll_influence));
699
700                    chassis.apply_impulse_at_point(side_impulse, impulse_point, false);
701
702                    // TODO: apply friction impulse on the ground
703                    // let ground_object = self.wheels[wheel_id].raycast_info.ground_object;
704                    // ground_object.apply_impulse_at_point(
705                    //     -side_impulse,
706                    //     wheels.raycast_info.contact_point_ws,
707                    //     false,
708                    // );
709                }
710            }
711        }
712    }
713}
714
715struct WheelContactPoint<'a> {
716    body0: &'a RigidBody,
717    body1: Option<&'a RigidBody>,
718    friction_position_world: Vector,
719    friction_direction_world: Vector,
720    jac_diag_ab_inv: Real,
721    max_impulse: Real,
722}
723
724impl<'a> WheelContactPoint<'a> {
725    pub fn new(
726        body0: &'a RigidBody,
727        body1: Option<&'a RigidBody>,
728        friction_position_world: Vector,
729        friction_direction_world: Vector,
730        max_impulse: Real,
731    ) -> Self {
732        fn impulse_denominator(body: &RigidBody, pos: Vector, n: Vector) -> Real {
733            let dpt = pos - body.center_of_mass();
734            let gcross = dpt.gcross(n);
735            let v = (body.mprops.effective_world_inv_inertia * gcross).gcross(dpt);
736            // TODO: take the effective inv mass into account instead of the inv_mass?
737            body.mprops.local_mprops.inv_mass + n.dot(v)
738        }
739        let denom0 = impulse_denominator(body0, friction_position_world, friction_direction_world);
740        let denom1 = body1
741            .map(|body1| {
742                impulse_denominator(body1, friction_position_world, friction_direction_world)
743            })
744            .unwrap_or(0.0);
745        let relaxation = 1.0;
746        let jac_diag_ab_inv = relaxation / (denom0 + denom1);
747
748        Self {
749            body0,
750            body1,
751            friction_position_world,
752            friction_direction_world,
753            jac_diag_ab_inv,
754            max_impulse,
755        }
756    }
757
758    pub fn calc_rolling_friction(&self, num_wheels_on_ground: usize) -> Real {
759        let contact_pos_world = self.friction_position_world;
760        let max_impulse = self.max_impulse;
761
762        let vel1 = self.body0.velocity_at_point(contact_pos_world);
763        let vel2 = self
764            .body1
765            .map(|b| b.velocity_at_point(contact_pos_world))
766            .unwrap_or_default();
767        let vel = vel1 - vel2;
768        let vrel = self.friction_direction_world.dot(vel);
769
770        // calculate friction that moves us to zero relative velocity
771        (-vrel * self.jac_diag_ab_inv / (num_wheels_on_ground as Real))
772            .clamp(-max_impulse, max_impulse)
773    }
774}
775
776fn resolve_single_bilateral(
777    body1: &RigidBody,
778    pt1: Vector,
779    body2: &RigidBody,
780    pt2: Vector,
781    normal: Vector,
782) -> Real {
783    let vel1 = body1.velocity_at_point(pt1);
784    let vel2 = body2.velocity_at_point(pt2);
785    let dvel = vel1 - vel2;
786
787    let dpt1 = pt1 - body1.center_of_mass();
788    let dpt2 = pt2 - body2.center_of_mass();
789    let aj = dpt1.gcross(normal);
790    let bj = dpt2.gcross(-normal);
791    let iaj = body1.mprops.effective_world_inv_inertia * aj;
792    let ibj = body2.mprops.effective_world_inv_inertia * bj;
793
794    // TODO: take the effective_inv_mass into account?
795    let im1 = body1.mprops.local_mprops.inv_mass;
796    let im2 = body2.mprops.local_mprops.inv_mass;
797
798    let jac_diag_ab = im1 + im2 + iaj.gdot(iaj) + ibj.gdot(ibj);
799    let jac_diag_ab_inv = crate::utils::inv(jac_diag_ab);
800    let rel_vel = normal.dot(dvel);
801
802    //todo: move this into proper structure
803    let contact_damping = 0.2;
804    -contact_damping * rel_vel * jac_diag_ab_inv
805}
806
807fn resolve_single_unilateral(body1: &RigidBody, pt1: Vector, normal: Vector) -> Real {
808    let vel1 = body1.velocity_at_point(pt1);
809    let dvel = vel1;
810    let dpt1 = pt1 - body1.center_of_mass();
811    let aj = dpt1.gcross(normal);
812    let iaj = body1.mprops.effective_world_inv_inertia * aj;
813
814    // TODO: take the effective_inv_mass into account?
815    let im1 = body1.mprops.local_mprops.inv_mass;
816    let jac_diag_ab = im1 + iaj.gdot(iaj);
817    let jac_diag_ab_inv = crate::utils::inv(jac_diag_ab);
818    let rel_vel = normal.dot(dvel);
819
820    //todo: move this into proper structure
821    let contact_damping = 0.2;
822    -contact_damping * rel_vel * jac_diag_ab_inv
823}