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.
11pub struct DynamicRayCastVehicleController {
12    wheels: Vec<Wheel>,
13    forward_ws: Vec<Vector<Real>>,
14    axle: Vec<Vector<Real>>,
15    /// The current forward speed of the vehicle.
16    pub current_vehicle_speed: Real,
17
18    /// Handle of the vehicle’s chassis.
19    pub chassis: RigidBodyHandle,
20    /// The chassis’ local _up_ direction (`0 = x, 1 = y, 2 = z`)
21    pub index_up_axis: usize,
22    /// The chassis’ local _forward_ direction (`0 = x, 1 = y, 2 = z`)
23    pub index_forward_axis: usize,
24}
25
26#[derive(Copy, Clone, Debug, PartialEq)]
27/// Parameters affecting the physical behavior of a wheel.
28pub struct WheelTuning {
29    /// The suspension stiffness.
30    ///
31    /// Increase this value if the suspension appears to not push the vehicle strong enough.
32    pub suspension_stiffness: Real,
33    /// The suspension’s damping when it is being compressed.
34    pub suspension_compression: Real,
35    /// The suspension’s damping when it is being released.
36    ///
37    /// Increase this value if the suspension appears to overshoot.
38    pub suspension_damping: Real,
39    /// The maximum distance the suspension can travel before and after its resting length.
40    pub max_suspension_travel: Real,
41    /// The multiplier of friction between a tire and the collider it's on top of.
42    pub side_friction_stiffness: Real,
43    /// Parameter controlling how much traction the tire has.
44    ///
45    /// The larger the value, the more instantaneous braking will happen (with the risk of
46    /// causing the vehicle to flip if it’s too strong).
47    pub friction_slip: Real,
48    /// The maximum force applied by the suspension.
49    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
66/// Objects used to initialize a wheel.
67struct WheelDesc {
68    /// The position of the wheel, relative to the chassis.
69    pub chassis_connection_cs: Point<Real>,
70    /// The direction of the wheel’s suspension, relative to the chassis.
71    ///
72    /// The ray-casting will happen following this direction to detect the ground.
73    pub direction_cs: Vector<Real>,
74    /// The wheel’s axle axis, relative to the chassis.
75    pub axle_cs: Vector<Real>,
76    /// The rest length of the wheel’s suspension spring.
77    pub suspension_rest_length: Real,
78    /// The maximum distance the suspension can travel before and after its resting length.
79    pub max_suspension_travel: Real,
80    /// The wheel’s radius.
81    pub radius: Real,
82
83    /// The suspension stiffness.
84    ///
85    /// Increase this value if the suspension appears to not push the vehicle strong enough.
86    pub suspension_stiffness: Real,
87    /// The suspension’s damping when it is being compressed.
88    pub damping_compression: Real,
89    /// The suspension’s damping when it is being released.
90    ///
91    /// Increase this value if the suspension appears to overshoot.
92    pub damping_relaxation: Real,
93    /// Parameter controlling how much traction the tire has.
94    ///
95    /// The larger the value, the more instantaneous braking will happen (with the risk of
96    /// causing the vehicle to flip if it’s too strong).
97    pub friction_slip: Real,
98    /// The maximum force applied by the suspension.
99    pub max_suspension_force: Real,
100    /// The multiplier of friction between a tire and the collider it's on top of.
101    pub side_friction_stiffness: Real,
102}
103
104#[derive(Copy, Clone, Debug, PartialEq)]
105/// A wheel attached to a vehicle.
106pub 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    /// The position of the wheel, relative to the chassis.
114    pub chassis_connection_point_cs: Point<Real>,
115    /// The direction of the wheel’s suspension, relative to the chassis.
116    ///
117    /// The ray-casting will happen following this direction to detect the ground.
118    pub direction_cs: Vector<Real>,
119    /// The wheel’s axle axis, relative to the chassis.
120    pub axle_cs: Vector<Real>,
121    /// The rest length of the wheel’s suspension spring.
122    pub suspension_rest_length: Real,
123    /// The maximum distance the suspension can travel before and after its resting length.
124    pub max_suspension_travel: Real,
125    /// The wheel’s radius.
126    pub radius: Real,
127    /// The suspension stiffness.
128    ///
129    /// Increase this value if the suspension appears to not push the vehicle strong enough.
130    pub suspension_stiffness: Real,
131    /// The suspension’s damping when it is being compressed.
132    pub damping_compression: Real,
133    /// The suspension’s damping when it is being released.
134    ///
135    /// Increase this value if the suspension appears to overshoot.
136    pub damping_relaxation: Real,
137    /// Parameter controlling how much traction the tire has.
138    ///
139    /// The larger the value, the more instantaneous braking will happen (with the risk of
140    /// causing the vehicle to flip if it’s too strong).
141    pub friction_slip: Real,
142    /// The multiplier of friction between a tire and the collider it's on top of.
143    pub side_friction_stiffness: Real,
144    /// The wheel’s current rotation on its axle.
145    pub rotation: Real,
146    delta_rotation: Real,
147    roll_influence: Real, // TODO: make this public?
148    /// The maximum force applied by the suspension.
149    pub max_suspension_force: Real,
150
151    /// The forward impulses applied by the wheel on the chassis.
152    pub forward_impulse: Real,
153    /// The side impulses applied by the wheel on the chassis.
154    pub side_impulse: Real,
155
156    /// The steering angle for this wheel.
157    pub steering: Real,
158    /// The forward force applied by this wheel on the chassis.
159    pub engine_force: Real,
160    /// The maximum amount of braking impulse applied to slow down the vehicle.
161    pub brake: Real,
162
163    clipped_inv_contact_dot_suspension: Real,
164    suspension_relative_velocity: Real,
165    /// The force applied by the suspension.
166    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    /// Information about suspension and the ground obtained from the ray-casting
205    /// for this wheel.
206    pub fn raycast_info(&self) -> &RayCastInfo {
207        &self.raycast_info
208    }
209
210    /// The world-space center of the wheel.
211    pub fn center(&self) -> Point<Real> {
212        self.center
213    }
214
215    /// The world-space direction of the wheel’s suspension.
216    pub fn suspension(&self) -> Vector<Real> {
217        self.wheel_direction_ws
218    }
219
220    /// The world-space direction of the wheel’s axle.
221    pub fn axle(&self) -> Vector<Real> {
222        self.wheel_axle_ws
223    }
224}
225
226/// Information about suspension and the ground obtained from the ray-casting
227/// to simulate a wheel’s suspension.
228#[derive(Copy, Clone, Debug, PartialEq, Default)]
229pub struct RayCastInfo {
230    /// The (world-space) contact normal between the wheel and the floor.
231    pub contact_normal_ws: Vector<Real>,
232    /// The (world-space) point hit by the wheel’s ray-cast.
233    pub contact_point_ws: Point<Real>,
234    /// The suspension length for the wheel.
235    pub suspension_length: Real,
236    /// The (world-space) starting point of the ray-cast.
237    pub hard_point_ws: Point<Real>,
238    /// Is the wheel in contact with the ground?
239    pub is_in_contact: bool,
240    /// The collider hit by the ray-cast.
241    pub ground_object: Option<ColliderHandle>,
242}
243
244impl DynamicRayCastVehicleController {
245    /// Creates a new vehicle represented by the given rigid-body.
246    ///
247    /// Wheels have to be attached afterwards calling [`Self::add_wheel`].
248    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    //
261    // basically most of the code is general for 2 or 4 wheel vehicles, but some of it needs to be reviewed
262    //
263    /// Adds a wheel to this vehicle.
264    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                    // If the hit is still not defined, set the normal.
351                    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            // clamp on max suspension travel
363            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            // No contact, put wheel info as in rest position
392            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    /// Updates the vehicle’s velocity based on its suspension, engine force, and brake.
400    #[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        //
418        // simulate suspension
419        //
420
421        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            // apply suspension force
439            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; //damping of rotation when not in contact
473        }
474    }
475
476    /// Reference to all the wheels attached to this vehicle.
477    pub fn wheels(&self) -> &[Wheel] {
478        &self.wheels
479    }
480
481    /// Mutable reference to all the wheels attached to this vehicle.
482    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                //	Spring
493                {
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                // Damper
504                {
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                // RESULT
517                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        //TODO: collapse all those loops into one!
538        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                //switch between active rolling (throttle), braking and non-active rolling friction (no throttle/break)
629
630                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        // apply the impulses
666        {
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                    // TODO: apply friction impulse on the ground
694                    // let ground_object = self.wheels[wheel_id].raycast_info.ground_object;
695                    // ground_object.apply_impulse_at_point(
696                    //     -side_impulse,
697                    //     wheels.raycast_info.contact_point_ws,
698                    //     false,
699                    // );
700                }
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            // TODO: take the effective inv mass into account instead of the inv_mass?
730            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        // calculate friction that moves us to zero relative velocity
765        (-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    // TODO: take the effective_inv_mass into account?
789    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    //todo: move this into proper structure
797    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    // TODO: take the effective_inv_mass into account?
809    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    //todo: move this into proper structure
815    let contact_damping = 0.2;
816    -contact_damping * rel_vel * jac_diag_ab_inv
817}