testbed: add gravity control + character controller speed control (#822)

This commit is contained in:
Thierry Berger
2025-07-11 15:31:58 +02:00
committed by GitHub
parent ad41b3d071
commit 9c9ba4570e
7 changed files with 87 additions and 51 deletions

View File

@@ -171,7 +171,7 @@ pub fn init_world(testbed: &mut Testbed) {
/*
* Callback to update the character based on user inputs.
*/
let mut control_mode = CharacterControlMode::Kinematic;
let mut control_mode = CharacterControlMode::Kinematic(0.1);
let mut controller = KinematicCharacterController {
max_slope_climb_angle: impossible_slope_angle - 0.02,
min_slope_slide_angle: impossible_slope_angle - 0.02,

View File

@@ -60,7 +60,7 @@ pub fn init_world(testbed: &mut Testbed) {
/*
* Callback to update the character based on user inputs.
*/
let mut control_mode = CharacterControlMode::Kinematic;
let mut control_mode = CharacterControlMode::Kinematic(0.1);
let mut controller = KinematicCharacterController::default();
let mut pid = PidController::default();

View File

@@ -8,10 +8,12 @@ use rapier_testbed2d::{
KeyCode, PhysicsState, TestbedGraphics,
};
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub type CharacterSpeed = Real;
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum CharacterControlMode {
Kinematic,
Pid,
Kinematic(CharacterSpeed),
Pid(CharacterSpeed),
}
pub fn update_character(
@@ -27,20 +29,20 @@ pub fn update_character(
if *control_mode != prev_control_mode {
match control_mode {
CharacterControlMode::Kinematic => physics.bodies[character_handle]
CharacterControlMode::Kinematic(_) => physics.bodies[character_handle]
.set_body_type(RigidBodyType::KinematicPositionBased, false),
CharacterControlMode::Pid => {
CharacterControlMode::Pid(_) => {
physics.bodies[character_handle].set_body_type(RigidBodyType::Dynamic, true)
}
}
}
match *control_mode {
CharacterControlMode::Kinematic => {
update_kinematic_controller(graphics, physics, character_handle, controller)
CharacterControlMode::Kinematic(speed) => {
update_kinematic_controller(graphics, physics, character_handle, controller, speed)
}
CharacterControlMode::Pid => {
update_pid_controller(graphics, physics, character_handle, pid)
CharacterControlMode::Pid(speed) => {
update_pid_controller(graphics, physics, character_handle, pid, speed)
}
}
}
@@ -48,7 +50,7 @@ pub fn update_character(
fn character_movement_from_inputs(
gfx: &TestbedGraphics,
mut speed: Real,
artificial_gravity: bool,
artificial_gravity: Option<Real>,
) -> Vector<Real> {
let mut desired_movement = Vector::zeros();
@@ -75,8 +77,8 @@ fn character_movement_from_inputs(
desired_movement *= speed;
if artificial_gravity {
desired_movement -= Vector::y() * speed;
if let Some(artificial_gravity) = artificial_gravity {
desired_movement += Vector::y() * artificial_gravity;
}
desired_movement
@@ -87,8 +89,10 @@ fn update_pid_controller(
phx: &mut PhysicsState,
character_handle: RigidBodyHandle,
pid: &mut PidController,
speed: Real,
) {
let desired_movement = character_movement_from_inputs(gfx, 0.1, false);
let desired_movement = character_movement_from_inputs(gfx, speed, None);
let character_body = &mut phx.bodies[character_handle];
// Adjust the controlled axis depending on the keys pressed by the user.
@@ -123,9 +127,9 @@ fn update_kinematic_controller(
phx: &mut PhysicsState,
character_handle: RigidBodyHandle,
controller: &KinematicCharacterController,
speed: Real,
) {
let speed = 0.1;
let desired_movement = character_movement_from_inputs(gfx, speed, true);
let desired_movement = character_movement_from_inputs(gfx, speed, Some(phx.gravity.y));
let character_body = &phx.bodies[character_handle];
let character_collider = &phx.colliders[character_body.colliders()[0]];
@@ -139,7 +143,7 @@ fn update_kinematic_controller(
&phx.query_pipeline,
character_collider.shape(),
character_collider.position(),
desired_movement.cast::<Real>(),
desired_movement,
QueryFilter::new().exclude_rigid_body(character_handle),
|c| collisions.push(c),
);
@@ -178,22 +182,26 @@ fn character_control_ui(
ComboBox::from_label("control mode")
.selected_text(format!("{:?}", *control_mode))
.show_ui(ui, |ui| {
ui.selectable_value(control_mode, CharacterControlMode::Kinematic, "Kinematic");
ui.selectable_value(control_mode, CharacterControlMode::Pid, "Pid");
ui.selectable_value(
control_mode,
CharacterControlMode::Kinematic(0.1),
"Kinematic",
);
ui.selectable_value(control_mode, CharacterControlMode::Pid(0.1), "Pid");
});
match control_mode {
CharacterControlMode::Kinematic => {
kinematic_control_ui(ui, character_controller);
CharacterControlMode::Kinematic(speed) => {
kinematic_control_ui(ui, character_controller, speed);
}
CharacterControlMode::Pid => {
pid_control_ui(ui, pid_controller);
CharacterControlMode::Pid(speed) => {
pid_control_ui(ui, pid_controller, speed);
}
}
});
}
fn pid_control_ui(ui: &mut Ui, pid_controller: &mut PidController) {
fn pid_control_ui(ui: &mut Ui, pid_controller: &mut PidController, speed: &mut Real) {
let mut lin_kp = pid_controller.pd.lin_kp.x;
let mut lin_ki = pid_controller.lin_ki.x;
let mut lin_kd = pid_controller.pd.lin_kd.x;
@@ -201,6 +209,7 @@ fn pid_control_ui(ui: &mut Ui, pid_controller: &mut PidController) {
let mut ang_ki = pid_controller.ang_ki;
let mut ang_kd = pid_controller.pd.ang_kd;
ui.add(Slider::new(speed, 0.0..=1.0).text("speed"));
ui.add(Slider::new(&mut lin_kp, 0.0..=100.0).text("linear Kp"));
ui.add(Slider::new(&mut lin_ki, 0.0..=10.0).text("linear Ki"));
ui.add(Slider::new(&mut lin_kd, 0.0..=1.0).text("linear Kd"));
@@ -216,7 +225,13 @@ fn pid_control_ui(ui: &mut Ui, pid_controller: &mut PidController) {
pid_controller.pd.ang_kd = ang_kd;
}
fn kinematic_control_ui(ui: &mut Ui, character_controller: &mut KinematicCharacterController) {
fn kinematic_control_ui(
ui: &mut Ui,
character_controller: &mut KinematicCharacterController,
speed: &mut Real,
) {
ui.add(Slider::new(speed, 0.0..=1.0).text("Speed"))
.on_hover_text("The speed applied each simulation tick.");
ui.checkbox(&mut character_controller.slide, "slide")
.on_hover_text("Should the character try to slide against the floor if it hits it?");
#[allow(clippy::useless_conversion)]

View File

@@ -187,7 +187,7 @@ pub fn init_world(testbed: &mut Testbed) {
/*
* Callback to update the character based on user inputs.
*/
let mut control_mode = CharacterControlMode::Kinematic;
let mut control_mode = CharacterControlMode::Kinematic(0.1);
let mut controller = KinematicCharacterController {
max_slope_climb_angle: impossible_slope_angle - 0.02,
min_slope_slide_angle: impossible_slope_angle - 0.02,

View File

@@ -88,7 +88,7 @@ pub fn init_world(testbed: &mut Testbed) {
/*
* Callback to update the character based on user inputs.
*/
let mut control_mode = CharacterControlMode::Kinematic;
let mut control_mode = CharacterControlMode::Kinematic(0.1);
let mut controller = KinematicCharacterController::default();
let mut pid = PidController::default();

View File

@@ -7,10 +7,12 @@ use rapier_testbed3d::{
KeyCode, PhysicsState, TestbedGraphics,
};
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub type CharacterSpeed = Real;
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum CharacterControlMode {
Kinematic,
Pid,
Kinematic(CharacterSpeed),
Pid(CharacterSpeed),
}
pub fn update_character(
@@ -26,20 +28,20 @@ pub fn update_character(
if *control_mode != prev_control_mode {
match control_mode {
CharacterControlMode::Kinematic => physics.bodies[character_handle]
CharacterControlMode::Kinematic(_) => physics.bodies[character_handle]
.set_body_type(RigidBodyType::KinematicPositionBased, false),
CharacterControlMode::Pid => {
CharacterControlMode::Pid(_) => {
physics.bodies[character_handle].set_body_type(RigidBodyType::Dynamic, true)
}
}
}
match *control_mode {
CharacterControlMode::Kinematic => {
update_kinematic_controller(graphics, physics, character_handle, controller)
CharacterControlMode::Kinematic(speed) => {
update_kinematic_controller(graphics, physics, character_handle, controller, speed)
}
CharacterControlMode::Pid => {
update_pid_controller(graphics, physics, character_handle, pid)
CharacterControlMode::Pid(speed) => {
update_pid_controller(graphics, physics, character_handle, pid, speed)
}
}
}
@@ -47,7 +49,7 @@ pub fn update_character(
fn character_movement_from_inputs(
gfx: &TestbedGraphics,
mut speed: Real,
artificial_gravity: bool,
artificial_gravity: Option<f32>,
) -> Vector<Real> {
let mut desired_movement = Vector::zeros();
@@ -86,8 +88,8 @@ fn character_movement_from_inputs(
desired_movement *= speed;
if artificial_gravity {
desired_movement -= Vector::y() * speed;
if let Some(artificial_gravity) = artificial_gravity {
desired_movement += Vector::y() * artificial_gravity;
}
desired_movement
@@ -98,8 +100,9 @@ fn update_pid_controller(
phx: &mut PhysicsState,
character_handle: RigidBodyHandle,
pid: &mut PidController,
speed: Real,
) {
let desired_movement = character_movement_from_inputs(gfx, 0.1, false);
let desired_movement = character_movement_from_inputs(gfx, speed, None);
let character_body = &mut phx.bodies[character_handle];
// Adjust the controlled axis depending on the keys pressed by the user.
@@ -134,9 +137,9 @@ fn update_kinematic_controller(
phx: &mut PhysicsState,
character_handle: RigidBodyHandle,
controller: &KinematicCharacterController,
speed: Real,
) {
let speed = 0.1;
let desired_movement = character_movement_from_inputs(gfx, speed, true);
let desired_movement = character_movement_from_inputs(gfx, speed, Some(phx.gravity.y));
let character_body = &phx.bodies[character_handle];
let character_collider = &phx.colliders[character_body.colliders()[0]];
@@ -189,22 +192,26 @@ fn character_control_ui(
ComboBox::from_label("control mode")
.selected_text(format!("{:?}", *control_mode))
.show_ui(ui, |ui| {
ui.selectable_value(control_mode, CharacterControlMode::Kinematic, "Kinematic");
ui.selectable_value(control_mode, CharacterControlMode::Pid, "Pid");
ui.selectable_value(
control_mode,
CharacterControlMode::Kinematic(0.1),
"Kinematic",
);
ui.selectable_value(control_mode, CharacterControlMode::Pid(0.1), "Pid");
});
match control_mode {
CharacterControlMode::Kinematic => {
kinematic_control_ui(ui, character_controller);
CharacterControlMode::Kinematic(speed) => {
kinematic_control_ui(ui, character_controller, speed);
}
CharacterControlMode::Pid => {
pid_control_ui(ui, pid_controller);
CharacterControlMode::Pid(speed) => {
pid_control_ui(ui, pid_controller, speed);
}
}
});
}
fn pid_control_ui(ui: &mut Ui, pid_controller: &mut PidController) {
fn pid_control_ui(ui: &mut Ui, pid_controller: &mut PidController, speed: &mut Real) {
let mut lin_kp = pid_controller.pd.lin_kp.x;
let mut lin_ki = pid_controller.lin_ki.x;
let mut lin_kd = pid_controller.pd.lin_kd.x;
@@ -212,6 +219,7 @@ fn pid_control_ui(ui: &mut Ui, pid_controller: &mut PidController) {
let mut ang_ki = pid_controller.ang_ki.x;
let mut ang_kd = pid_controller.pd.ang_kd.x;
ui.add(Slider::new(speed, 0.0..=1.0).text("speed"));
ui.add(Slider::new(&mut lin_kp, 0.0..=100.0).text("linear Kp"));
ui.add(Slider::new(&mut lin_ki, 0.0..=10.0).text("linear Ki"));
ui.add(Slider::new(&mut lin_kd, 0.0..=1.0).text("linear Kd"));
@@ -227,7 +235,13 @@ fn pid_control_ui(ui: &mut Ui, pid_controller: &mut PidController) {
pid_controller.pd.ang_kd.fill(ang_kd);
}
fn kinematic_control_ui(ui: &mut Ui, character_controller: &mut KinematicCharacterController) {
fn kinematic_control_ui(
ui: &mut Ui,
character_controller: &mut KinematicCharacterController,
speed: &mut Real,
) {
ui.add(Slider::new(speed, 0.0..=1.0).text("Speed"))
.on_hover_text("The speed applied each simulation tick.");
ui.checkbox(&mut character_controller.slide, "slide")
.on_hover_text("Should the character try to slide against the floor if it hits it?");
#[allow(clippy::useless_conversion)]

View File

@@ -235,6 +235,13 @@ pub(crate) fn update_ui(
let mut frequency = integration_parameters.inv_dt().round() as u32;
ui.add(Slider::new(&mut frequency, 0..=240).text("frequency (Hz)"));
let mut gravity_y = harness.physics.gravity.y;
if ui
.add(Slider::new(&mut gravity_y, 0.0..=-200.0).text("Gravity"))
.changed()
{
harness.physics.gravity.y = gravity_y;
}
integration_parameters.set_inv_dt(frequency as Real);
let mut sleep = state.flags.contains(TestbedStateFlags::SLEEP);