Skip to main content

bevy_rapier3d/control/
vehicle_controller.rs

1use crate::control::ControllerFilterPredicate;
2use crate::geometry::CollisionGroups;
3use crate::math::{Real, Vect};
4use crate::reflect::WheelTuningWrapper;
5use bevy::ecs::entity::EntityHashSet;
6use bevy::prelude::*;
7use rapier::control::DynamicRayCastVehicleController;
8use rapier::prelude::QueryFilterFlags;
9
10pub use rapier::control::WheelTuning;
11
12/// The state of a [`VehicleWheel`] computed by the plugin at the last vehicle update.
13///
14/// World-space quantities are expressed relative to the chassis pose before the last
15/// simulation step.
16#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
17#[reflect(Default, PartialEq, Debug)]
18pub struct VehicleWheelState {
19    /// The wheel's current rotation angle (in radians) around its axle.
20    ///
21    /// This increases when the vehicle rolls along the wheel's forward direction
22    /// (`contact_normal × axle`).
23    pub rotation: Real,
24    /// The current length of the wheel's suspension.
25    pub suspension_length: Real,
26    /// Is the wheel in contact with the ground?
27    pub is_in_contact: bool,
28    /// The world-space point hit by the wheel's ray-cast.
29    pub contact_point: Vect,
30    /// The world-space contact normal between the wheel and the ground.
31    pub contact_normal: Vect,
32    /// The world-space starting point of the wheel's ray-cast.
33    pub hard_point: Vect,
34    /// The world-space center of the wheel.
35    pub center: Vect,
36    /// The world-space direction of the wheel's suspension.
37    pub suspension_direction: Vect,
38    /// The world-space direction of the wheel's axle, including steering.
39    pub axle_direction: Vect,
40    /// The collider entity hit by the wheel's ray-cast, if any.
41    pub ground_entity: Option<Entity>,
42    /// The force applied by the suspension.
43    pub suspension_force: Real,
44    /// The forward impulse applied by the wheel on the chassis.
45    pub forward_impulse: Real,
46    /// The side impulse applied by the wheel on the chassis.
47    pub side_impulse: Real,
48}
49
50/// A wheel of a [`RayCastVehicleController`].
51///
52/// The description fields (connection point, directions, lengths, tuning) and the controls
53/// (engine force, brake, steering) can be modified at any time. The [`Self::state`] is
54/// written by the plugin at each vehicle update.
55#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
56#[reflect(Default, PartialEq, Debug)]
57pub struct VehicleWheel {
58    /// The position of the wheel's suspension attachment, in the chassis' local space.
59    pub chassis_connection_point: Vect,
60    /// The direction of the wheel's suspension, in the chassis' local space.
61    ///
62    /// The ray-cast detecting the ground follows this direction.
63    pub direction: Vect,
64    /// The wheel's axle axis, in the chassis' local space.
65    pub axle: Vect,
66    /// The rest length of the wheel's suspension spring.
67    pub suspension_rest_length: Real,
68    /// The wheel's radius.
69    pub radius: Real,
70    /// Parameters affecting the physical behavior of the wheel.
71    #[reflect(remote = WheelTuningWrapper)]
72    pub tuning: WheelTuning,
73    /// The forward force applied by this wheel on the chassis.
74    pub engine_force: Real,
75    /// The maximum braking impulse applied by this wheel to slow down the vehicle.
76    pub brake: Real,
77    /// The steering angle (in radians) of this wheel, around the suspension direction.
78    pub steering: Real,
79    /// The state of the wheel, written back by the plugin.
80    pub state: VehicleWheelState,
81}
82
83impl Default for VehicleWheel {
84    fn default() -> Self {
85        Self {
86            chassis_connection_point: Vect::ZERO,
87            direction: -Vect::Y,
88            axle: Vect::Z,
89            suspension_rest_length: 0.5,
90            radius: 0.5,
91            tuning: WheelTuning::default(),
92            engine_force: 0.0,
93            brake: 0.0,
94            steering: 0.0,
95            state: VehicleWheelState::default(),
96        }
97    }
98}
99
100impl VehicleWheel {
101    /// Creates a wheel with the given geometry, the given tuning, and no engine force,
102    /// brake, or steering.
103    pub fn new(
104        chassis_connection_point: Vect,
105        direction: Vect,
106        axle: Vect,
107        suspension_rest_length: Real,
108        radius: Real,
109        tuning: WheelTuning,
110    ) -> Self {
111        Self {
112            chassis_connection_point,
113            direction,
114            axle,
115            suspension_rest_length,
116            radius,
117            tuning,
118            ..Default::default()
119        }
120    }
121
122    /// The wheel's transform relative to the chassis, for rendering.
123    ///
124    /// The translation is the wheel center at the current suspension length, and the rotation
125    /// combines steering (around the suspension direction) and spin (around the axle).
126    pub fn local_transform(&self) -> Transform {
127        let translation =
128            self.chassis_connection_point + self.direction * self.state.suspension_length;
129        let steering = Quat::from_axis_angle(-self.direction.normalize_or_zero(), self.steering);
130        // Rolling forward along `normal × axle` is a negative rotation around the axle.
131        let spin = Quat::from_axis_angle(self.axle.normalize_or_zero(), -self.state.rotation);
132        Transform::from_translation(translation).with_rotation(steering * spin)
133    }
134
135    pub(crate) fn sync_to_raw(&self, raw: &mut rapier::control::Wheel) {
136        raw.chassis_connection_point_cs = self.chassis_connection_point;
137        raw.direction_cs = self.direction;
138        raw.axle_cs = self.axle;
139        raw.suspension_rest_length = self.suspension_rest_length;
140        raw.radius = self.radius;
141        raw.suspension_stiffness = self.tuning.suspension_stiffness;
142        raw.damping_compression = self.tuning.suspension_compression;
143        raw.damping_relaxation = self.tuning.suspension_damping;
144        raw.max_suspension_travel = self.tuning.max_suspension_travel;
145        raw.side_friction_stiffness = self.tuning.side_friction_stiffness;
146        raw.friction_slip = self.tuning.friction_slip;
147        raw.max_suspension_force = self.tuning.max_suspension_force;
148        raw.engine_force = self.engine_force;
149        raw.brake = self.brake;
150        raw.steering = self.steering;
151    }
152}
153
154/// A vehicle controller simulating wheels with ray-casts, attached to the chassis rigid-body.
155///
156/// Before each simulation step, the plugin casts one ray per wheel along its suspension
157/// direction, then applies suspension, engine, braking and friction impulses to the chassis,
158/// which must be a dynamic rigid-body on the same entity. Colliders attached to the chassis
159/// rigid-body are always ignored by the ray-casts. The results are written back into each
160/// wheel's [`VehicleWheel::state`] and into [`Self::current_vehicle_speed`].
161///
162/// The impulses are computed once per plugin update using the last simulation timestep, so
163/// this is best used with a fixed timestep.
164#[derive(Clone, Debug, Component, Reflect)]
165#[reflect(Component, Default, Debug)]
166pub struct RayCastVehicleController {
167    /// The wheels of this vehicle.
168    ///
169    /// Wheels can be added, removed or modified at any time.
170    pub wheels: Vec<VehicleWheel>,
171    /// The chassis' local up axis (`0 = x, 1 = y, 2 = z`).
172    pub index_up_axis: usize,
173    /// The chassis' local forward axis (`0 = x, 1 = y, 2 = z`).
174    ///
175    /// This is only used to determine the sign of [`Self::current_vehicle_speed`].
176    pub index_forward_axis: usize,
177    /// Flags for excluding some categories of colliders from the wheels' ray-casts.
178    #[reflect(ignore)]
179    pub filter_flags: QueryFilterFlags,
180    /// Groups for excluding some colliders from the wheels' ray-casts.
181    pub filter_groups: Option<CollisionGroups>,
182    /// Colliders (identified by their entity) ignored by the wheels' ray-casts.
183    pub exclude_colliders: EntityHashSet,
184    /// Rigid-bodies (identified by their entity) whose colliders are all ignored by the
185    /// wheels' ray-casts.
186    pub exclude_rigid_bodies: EntityHashSet,
187    /// If set, any collider for which this predicate returns `false` is ignored by the wheels'
188    /// ray-casts. The colliders with a [`ControllerIgnored`](crate::control::ControllerIgnored)
189    /// component (or attached to a rigid-body with one) are always ignored.
190    #[reflect(ignore)]
191    pub filter_predicate: Option<ControllerFilterPredicate>,
192    /// The current forward speed of the vehicle, written back by the plugin.
193    ///
194    /// It is negative if the vehicle moves backward along its forward axis.
195    pub current_vehicle_speed: Real,
196    /// The underlying Rapier controller, created and synchronized automatically by the plugin.
197    ///
198    /// Its wheels are overwritten from [`Self::wheels`] before each update.
199    #[reflect(ignore)]
200    pub raw: Option<DynamicRayCastVehicleController>,
201}
202
203impl Default for RayCastVehicleController {
204    fn default() -> Self {
205        Self {
206            wheels: vec![],
207            index_up_axis: 1,
208            index_forward_axis: 0,
209            filter_flags: QueryFilterFlags::EXCLUDE_SENSORS,
210            filter_groups: None,
211            exclude_colliders: EntityHashSet::default(),
212            exclude_rigid_bodies: EntityHashSet::default(),
213            filter_predicate: None,
214            current_vehicle_speed: 0.0,
215            raw: None,
216        }
217    }
218}
219
220impl RayCastVehicleController {
221    /// Creates a vehicle controller with the given wheels.
222    pub fn new(wheels: Vec<VehicleWheel>) -> Self {
223        Self {
224            wheels,
225            ..Default::default()
226        }
227    }
228
229    /// Adds a wheel to this vehicle.
230    pub fn add_wheel(&mut self, wheel: VehicleWheel) -> &mut Self {
231        self.wheels.push(wheel);
232        self
233    }
234
235    /// Is any wheel of this vehicle in contact with the ground?
236    pub fn any_wheel_in_contact(&self) -> bool {
237        self.wheels.iter().any(|w| w.state.is_in_contact)
238    }
239}