Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix character max/min slope #701

Merged
merged 21 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d2c9c35
update to bevy 0.14 + add some control for testbed character controller
Vrixyz Jul 24, 2024
5a03a09
adapt example + add simpler test to reproduce max slope bug
Vrixyz Jul 25, 2024
63233f6
fix character controller max slope + add tests + better testbed setup
Vrixyz Jul 25, 2024
222c2ec
Update src/control/character_controller.rs
Vrixyz Jul 25, 2024
64c810e
fix some clippy warnings + better fix for grounded status
Vrixyz Jul 26, 2024
175316b
Merge branch 'testbed_character_controller' of github.com:Vrixyz/rapi…
Vrixyz Jul 26, 2024
2a04835
reinstate nudge factor
Vrixyz Jul 29, 2024
e4c923e
more margin for impossible slope example + as much possible reuse ini…
Vrixyz Jul 30, 2024
c1fe32b
lower nudge factor to avoid messing too much with ground detection
Vrixyz Jul 30, 2024
9862850
slightly better comments
Vrixyz Jul 30, 2024
a9c5b68
revert ground change + better ci test
Vrixyz Jul 30, 2024
7204849
remove unused comments + spaces polish
Vrixyz Jul 30, 2024
bccd234
polish testbec character ui
Vrixyz Jul 30, 2024
8cdb21a
fmt
Vrixyz Jul 30, 2024
c6b359d
Merge remote-tracking branch 'upstream' into testbed_character_contro…
Vrixyz Aug 5, 2024
0bd887b
fix clippy
Vrixyz Aug 5, 2024
553d164
fix test f64 warnings
Vrixyz Aug 9, 2024
160fbb3
fix other warnings
Vrixyz Aug 9, 2024
c6420f6
Merge remote-tracking branch 'upstream' into testbed_character_contro…
Vrixyz Sep 9, 2024
677ed84
Merge branch 'master' into testbed_character_controller
Vrixyz Sep 23, 2024
98b58e6
update changelog
Vrixyz Sep 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- The region key has been replaced by an i64 in the f64 version of rapier, increasing the range before panics occur.
- Fix `BroadphaseMultiSap` not being able to serialize correctly with serde_json.
- Fix `KinematicCharacterController::move_shape` not respecting parameters `max_slope_climb_angle` and `min_slope_slide_angle`.

### Added

Expand Down
1 change: 0 additions & 1 deletion crates/rapier_testbed2d-f64/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ crossbeam = "0.8"
bincode = "1"
Inflector = "0.11"
md5 = "0.7"

bevy_egui = "0.29"
bevy_ecs = "0.14"
bevy_core_pipeline = "0.14"
Expand Down
1 change: 0 additions & 1 deletion crates/rapier_testbed2d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ crossbeam = "0.8"
bincode = "1"
Inflector = "0.11"
md5 = "0.7"

bevy_egui = "0.29"
bevy_ecs = "0.14"
bevy_core_pipeline = "0.14"
Expand Down
1 change: 0 additions & 1 deletion crates/rapier_testbed3d-f64/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ bincode = "1"
md5 = "0.7"
Inflector = "0.11"
serde = { version = "1", features = ["derive"] }

bevy_egui = "0.29"
bevy_ecs = "0.14"
bevy_core_pipeline = "0.14"
Expand Down
1 change: 0 additions & 1 deletion crates/rapier_testbed3d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ bincode = "1"
md5 = "0.7"
Inflector = "0.11"
serde = { version = "1", features = ["derive"] }

bevy_egui = "0.29"
bevy_ecs = "0.14"
bevy_core_pipeline = "0.14"
Expand Down
26 changes: 14 additions & 12 deletions examples3d/character_controller3.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rapier3d::prelude::*;
use rapier3d::{control::KinematicCharacterController, prelude::*};
use rapier_testbed3d::Testbed;

pub fn init_world(testbed: &mut Testbed) {
Expand Down Expand Up @@ -41,7 +41,7 @@ pub fn init_world(testbed: &mut Testbed) {
* Character we will control manually.
*/
let rigid_body =
RigidBodyBuilder::kinematic_position_based().translation(vector![-3.0, 5.0, 0.0] * scale);
RigidBodyBuilder::kinematic_position_based().translation(vector![0.0, 0.5, 0.0] * scale);
let character_handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::capsule_y(0.3 * scale, 0.15 * scale); // 0.15, 0.3, 0.15);
colliders.insert_with_parent(collider, character_handle, &mut bodies);
Expand Down Expand Up @@ -95,19 +95,15 @@ pub fn init_world(testbed: &mut Testbed) {
*/
let slope_angle = 0.2;
let slope_size = 2.0;
let collider = ColliderBuilder::cuboid(
slope_size * scale,
ground_height * scale,
slope_size * scale,
)
.translation(vector![ground_size + slope_size, -ground_height + 0.4, 0.0] * scale)
.rotation(Vector::z() * slope_angle);
let collider = ColliderBuilder::cuboid(slope_size, ground_height, slope_size)
.translation(vector![0.1 + slope_size, -ground_height + 0.4, 0.0])
.rotation(Vector::z() * slope_angle);
Comment on lines -103 to +100
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scale to add back ; not sure it's still usable though 🤔

colliders.insert(collider);

/*
* Create a slope we can’t climb.
*/
let impossible_slope_angle = 0.9;
let impossible_slope_angle = 0.6;
let impossible_slope_size = 2.0;
let collider = ColliderBuilder::cuboid(
slope_size * scale,
Expand All @@ -116,8 +112,8 @@ pub fn init_world(testbed: &mut Testbed) {
)
.translation(
vector![
ground_size + slope_size * 2.0 + impossible_slope_size - 0.9,
-ground_height + 2.3,
0.1 + slope_size * 2.0 + impossible_slope_size - 0.9,
-ground_height + 1.7,
0.0
] * scale,
)
Expand Down Expand Up @@ -185,5 +181,11 @@ pub fn init_world(testbed: &mut Testbed) {
*/
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
testbed.set_character_body(character_handle);
testbed.set_character_controller(Some(KinematicCharacterController {
max_slope_climb_angle: impossible_slope_angle - 0.02,
min_slope_slide_angle: impossible_slope_angle - 0.02,
slide: true,
..Default::default()
}));
testbed.look_at(point!(10.0, 10.0, 10.0), Point::origin());
}
2 changes: 1 addition & 1 deletion examples3d/debug_internal_edges3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn init_world(testbed: &mut Testbed) {
colliders.insert_with_parent(collider, handle, &mut bodies);

let rigid_body = RigidBodyBuilder::dynamic()
.translation(vector![0.0, 0.5, 0.0])
.translation(vector![-3.0, 5.0, 0.0])
.linvel(vector![0.0, -4.0, 20.0])
.can_sleep(false);
let handle = bodies.insert(rigid_body);
Expand Down
157 changes: 153 additions & 4 deletions src/control/character_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ impl Default for KinematicCharacterController {
}

/// The effective movement computed by the character controller.
#[derive(Debug)]
pub struct EffectiveCharacterMovement {
/// The movement to apply.
pub translation: Vector<Real>,
Expand Down Expand Up @@ -542,17 +543,17 @@ impl KinematicCharacterController {
) -> Vector<Real> {
let [_vertical_input, horizontal_input] = self.split_into_components(movement_input);
let horiz_input_decomp = self.decompose_hit(&horizontal_input, &hit.toi);
let input_decomp = self.decompose_hit(movement_input, &hit.toi);

let decomp = self.decompose_hit(translation_remaining, &hit.toi);

// An object is trying to slip if the tangential movement induced by its vertical movement
// points downward.
let slipping_intent = self.up.dot(&horiz_input_decomp.vertical_tangent) < 0.0;
// An object is slipping if its vertical movement points downward.
let slipping = self.up.dot(&decomp.vertical_tangent) < 0.0;

// An object is trying to climb if its indirect vertical motion points upward.
let climbing_intent = self.up.dot(&input_decomp.vertical_tangent) > 0.0;
// An object is trying to climb if its vertical input motion points upward.
let climbing_intent = self.up.dot(&_vertical_input) > 0.0;
// An object is climbing if the tangential movement induced by its vertical movement points upward.
Comment on lines -554 to +556
Copy link
Contributor Author

@Vrixyz Vrixyz Jul 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in my understanding, "climbing intent" refers to an explicit movement going upward of the slope, that's why I'm testing only the input, and not the decomposition according to the hit.

the naming is confusing because slipping_intent doesn't refer exactly to the same thing, but I'm not sure how to rename.

let climbing = self.up.dot(&decomp.vertical_tangent) > 0.0;

let allowed_movement = if hit.is_wall && climbing && !climbing_intent {
Expand Down Expand Up @@ -904,3 +905,151 @@ fn subtract_hit(translation: Vector<Real>, hit: &ShapeCastHit) -> Vector<Real> {
let surface_correction = surface_correction * (1.0 + 1.0e-5);
translation + *hit.normal1 * surface_correction
}

#[cfg(all(feature = "dim3", feature = "f32"))]
#[cfg(test)]
mod test {
use crate::{control::KinematicCharacterController, prelude::*};

#[test]
fn character_controller_climb_test() {
let mut colliders = ColliderSet::new();
let mut impulse_joints = ImpulseJointSet::new();
let mut multibody_joints = MultibodyJointSet::new();
let mut pipeline = PhysicsPipeline::new();
let mut bf = BroadPhaseMultiSap::new();
let mut nf = NarrowPhase::new();
let mut islands = IslandManager::new();
let mut query_pipeline = QueryPipeline::new();

let mut bodies = RigidBodySet::new();

let gravity = Vector::y() * -9.81;

let ground_size = 100.0;
let ground_height = 0.1;
/*
* Create a flat ground
*/
let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
let floor_handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
colliders.insert_with_parent(collider, floor_handle, &mut bodies);

/*
* Create a slope we can climb.
*/
let slope_angle = 0.2;
let slope_size = 2.0;
let collider = ColliderBuilder::cuboid(slope_size, ground_height, slope_size)
.translation(vector![0.1 + slope_size, -ground_height + 0.4, 0.0])
.rotation(Vector::z() * slope_angle);
colliders.insert(collider);

/*
* Create a slope we can’t climb.
*/
let impossible_slope_angle = 0.6;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on master, this impossible angle can be climb in this test. That's what https://github.com/dimforge/rapier/pull/701/files#r1697044257 fixes.

let impossible_slope_size = 2.0;
let collider = ColliderBuilder::cuboid(slope_size, ground_height, ground_size)
.translation(vector![
0.1 + slope_size * 2.0 + impossible_slope_size - 0.9,
-ground_height + 1.7,
0.0
])
.rotation(Vector::z() * impossible_slope_angle);
colliders.insert(collider);

let integration_parameters = IntegrationParameters::default();

// Initialize character which can climb
let mut character_body_can_climb = RigidBodyBuilder::kinematic_position_based()
.additional_mass(1.0)
.build();
character_body_can_climb.set_translation(Vector::new(0.6, 0.5, 0.0), false);
let character_handle_can_climb = bodies.insert(character_body_can_climb);

let collider = ColliderBuilder::ball(0.5).build();
colliders.insert_with_parent(collider.clone(), character_handle_can_climb, &mut bodies);

// Initialize character which cannot climb
let mut character_body_cannot_climb = RigidBodyBuilder::kinematic_position_based()
.additional_mass(1.0)
.build();
character_body_cannot_climb.set_translation(Vector::new(-0.6, 0.5, 0.0), false);
let character_handle_cannot_climb = bodies.insert(character_body_cannot_climb);

let collider = ColliderBuilder::ball(0.5).build();
let character_shape = collider.shape();
colliders.insert_with_parent(collider.clone(), character_handle_cannot_climb, &mut bodies);

query_pipeline.update(&colliders);
for i in 0..200 {
let mut update_character_controller =
|controller: KinematicCharacterController, handle: RigidBodyHandle| {
let character_body = bodies.get(handle).unwrap();
// Use a closure to handle or collect the collisions while
// the character is being moved.
let mut collisions = vec![];
let filter_character_controller = QueryFilter::new().exclude_rigid_body(handle);
let effective_movement = controller.move_shape(
integration_parameters.dt,
&bodies,
&colliders,
&query_pipeline,
character_shape,
character_body.position(),
Vector::new(0.1, -0.1, 0.0),
filter_character_controller,
|collision| collisions.push(collision),
);
let character_body = bodies.get_mut(handle).unwrap();
let translation = character_body.translation();
assert_eq!(
effective_movement.grounded, true,
"movement should be grounded at all times for current setup (iter: {}), pos: {}.",
i, translation + effective_movement.translation
);
character_body.set_next_kinematic_translation(
translation + effective_movement.translation,
);
};

let character_controller_cannot_climb = KinematicCharacterController {
max_slope_climb_angle: impossible_slope_angle - 0.001,
..Default::default()
};
let character_controller_can_climb = KinematicCharacterController {
max_slope_climb_angle: impossible_slope_angle + 0.001,
..Default::default()
};
update_character_controller(
character_controller_cannot_climb,
character_handle_cannot_climb,
);
update_character_controller(character_controller_can_climb, character_handle_can_climb);
// Step once
pipeline.step(
&gravity,
&integration_parameters,
&mut islands,
&mut bf,
&mut nf,
&mut bodies,
&mut colliders,
&mut impulse_joints,
&mut multibody_joints,
&mut CCDSolver::new(),
Some(&mut query_pipeline),
&(),
&(),
);
}
let character_body = bodies.get(character_handle_can_climb).unwrap();
assert!(character_body.translation().x > 6.0);
assert!(character_body.translation().y > 3.0);
let character_body = bodies.get(character_handle_cannot_climb).unwrap();
assert!(character_body.translation().x < 4.0);
assert!(dbg!(character_body.translation().y) < 2.0);
}
}
8 changes: 7 additions & 1 deletion src_testbed/testbed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ pub struct TestbedState {
pub draw_colls: bool,
pub highlighted_body: Option<RigidBodyHandle>,
pub character_body: Option<RigidBodyHandle>,
pub character_controller: Option<KinematicCharacterController>,
#[cfg(feature = "dim3")]
pub vehicle_controller: Option<DynamicRayCastVehicleController>,
// pub grabbed_object: Option<DefaultBodyPartHandle>,
Expand Down Expand Up @@ -186,6 +187,7 @@ impl TestbedApp {
draw_colls: false,
highlighted_body: None,
character_body: None,
character_controller: None,
#[cfg(feature = "dim3")]
vehicle_controller: None,
// grabbed_object: None,
Expand Down Expand Up @@ -530,6 +532,10 @@ impl<'a, 'b, 'c, 'd, 'e, 'f> Testbed<'a, 'b, 'c, 'd, 'e, 'f> {
self.state.character_body = Some(handle);
}

pub fn set_character_controller(&mut self, controller: Option<KinematicCharacterController>) {
self.state.character_controller = controller;
}

#[cfg(feature = "dim3")]
pub fn set_vehicle_controller(&mut self, controller: DynamicRayCastVehicleController) {
self.state.vehicle_controller = Some(controller);
Expand Down Expand Up @@ -827,7 +833,7 @@ impl<'a, 'b, 'c, 'd, 'e, 'f> Testbed<'a, 'b, 'c, 'd, 'e, 'f> {
desired_movement *= speed;
desired_movement -= Vector::y() * speed;

let controller = KinematicCharacterController::default();
let controller = self.state.character_controller.unwrap_or_default();
let phx = &mut self.harness.physics;
let character_body = &phx.bodies[character_handle];
let character_collider = &phx.colliders[character_body.colliders()[0]];
Expand Down
34 changes: 34 additions & 0 deletions src_testbed/ui.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use rapier::control::CharacterLength;
use rapier::counters::Counters;
use rapier::math::Real;
use std::num::NonZeroUsize;
Expand Down Expand Up @@ -240,7 +241,40 @@ pub fn update_ui(
// .set(TestbedStateFlags::CONTACT_POINTS, contact_points);
// state.flags.set(TestbedStateFlags::WIREFRAME, wireframe);
ui.separator();
if let Some(character_controller) = &mut state.character_controller {
ui.label("Character controller");
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)]
{

ui.add(Slider::new(&mut character_controller.max_slope_climb_angle, 0.0..=std::f32::consts::TAU.into()).text("max_slope_climb_angle"))
.on_hover_text("The maximum angle (radians) between the floor’s normal and the `up` vector that the character is able to climb.");
ui.add(Slider::new(&mut character_controller.min_slope_slide_angle, 0.0..=std::f32::consts::FRAC_PI_2.into()).text("min_slope_slide_angle"))
.on_hover_text("The minimum angle (radians) between the floor’s normal and the `up` vector before the character starts to slide down automatically.");
}
let mut is_snapped = character_controller.snap_to_ground.is_some();
if ui.checkbox(&mut is_snapped, "snap_to_ground").changed {
match is_snapped {
true => {
character_controller.snap_to_ground = Some(CharacterLength::Relative(0.1));
},
false => {
character_controller.snap_to_ground = None;
},
}
}
if let Some(snapped) = &mut character_controller.snap_to_ground {
match snapped {
CharacterLength::Relative(val) => {
ui.add(Slider::new(val, 0.0..=10.0).text("Snapped Relative Character Length"));
},
CharacterLength::Absolute(val) => {
ui.add(Slider::new(val, 0.0..=10.0).text("Snapped Absolute Character Length"));
},
}
}
ui.separator();
}
let label = if state.running == RunMode::Stop {
"Start (T)"
} else {
Expand Down