From 3fcca5c2eb98eb402c3c3bd60bd9c1e10d5d8022 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 12 Aug 2024 14:08:46 +0200 Subject: [PATCH 1/3] fix ground status detection, but test is failing --- src/control/character_controller.rs | 153 +++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 3 deletions(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index e2b48004b..c624ef729 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -250,6 +250,7 @@ impl KinematicCharacterController { let mut max_iters = 20; let mut kinematic_friction_translation = Vector::zeros(); let offset = self.offset.eval(dims.y); + let mut is_moving = false; while let Some((translation_dir, translation_dist)) = UnitVector::try_new_and_get(translation_remaining, 1.0e-5) @@ -259,6 +260,7 @@ impl KinematicCharacterController { } else { max_iters -= 1; } + is_moving = true; // 2. Cast towards the movement direction. if let Some((handle, hit)) = queries.cast_shape( @@ -318,9 +320,20 @@ impl KinematicCharacterController { // No interference along the path. result.translation += translation_remaining; translation_remaining.fill(0.0); + result.grounded = self.detect_grounded_status_and_apply_friction( + dt, + bodies, + colliders, + queries, + character_shape, + &(Translation::from(result.translation) * character_pos), + &dims, + filter, + None, + None, + ); break; } - result.grounded = self.detect_grounded_status_and_apply_friction( dt, bodies, @@ -338,6 +351,22 @@ impl KinematicCharacterController { break; } } + // When not moving, `detect_grounded_status_and_apply_friction` is not reached + // so we call it explicitly here. + if !is_moving { + result.grounded = self.detect_grounded_status_and_apply_friction( + dt, + bodies, + colliders, + queries, + character_shape, + &(Translation::from(result.translation) * character_pos), + &dims, + filter, + None, + None, + ); + } // If needed, and if we are not already grounded, snap to the ground. if grounded_at_starting_pos { self.snap_to_ground( @@ -397,7 +426,7 @@ impl KinematicCharacterController { } fn predict_ground(&self, up_extends: Real) -> Real { - self.offset.eval(up_extends) * 1.2 + self.offset.eval(up_extends) + 0.05 } fn detect_grounded_status_and_apply_friction( @@ -507,7 +536,6 @@ impl KinematicCharacterController { } true }); - grounded } @@ -904,3 +932,122 @@ fn subtract_hit(translation: Vector, hit: &ShapeCastHit) -> Vector { 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::{CharacterLength, KinematicCharacterController}, + prelude::*, + }; + + #[test] + fn character_controller_ground_detection() { + 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 = 600.0; + let ground_height = 0.1; + /* + * Create a flat ground + */ + let rigid_body = + RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height / 2f32, 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); + + let integration_parameters = IntegrationParameters::default(); + + // Initialize character with snap to ground + let character_controller_snap = KinematicCharacterController { + snap_to_ground: Some(CharacterLength::Relative(0.2)), + ..Default::default() + }; + let mut character_body_snap = RigidBodyBuilder::kinematic_position_based() + .additional_mass(1.0) + .build(); + character_body_snap.set_translation(Vector::new(0.6, 0.5, 0.0), false); + let character_handle_snap = bodies.insert(character_body_snap); + + let collider = ColliderBuilder::ball(0.5).build(); + colliders.insert_with_parent(collider.clone(), character_handle_snap, &mut bodies); + + // Initialize character without snap to ground + let character_controller_no_snap = KinematicCharacterController { + snap_to_ground: None, + ..Default::default() + }; + let mut character_body_no_snap = RigidBodyBuilder::kinematic_position_based() + .additional_mass(1.0) + .build(); + character_body_no_snap.set_translation(Vector::new(-0.6, 0.5, 0.0), false); + let character_handle_no_snap = bodies.insert(character_body_no_snap); + + let collider = ColliderBuilder::ball(0.5).build(); + let character_shape = collider.shape(); + colliders.insert_with_parent(collider.clone(), character_handle_no_snap, &mut bodies); + + query_pipeline.update(&colliders); + for i in 0..5200 { + 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.02), + 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, + ); + }; + + update_character_controller(character_controller_no_snap, character_handle_no_snap); + update_character_controller(character_controller_snap, character_handle_snap); + // 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), + &(), + &(), + ); + } + } +} From 634acef92bf30256b9be2ad7fdf50bc5c2999b03 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 12 Aug 2024 14:25:47 +0200 Subject: [PATCH 2/3] test passing --- src/control/character_controller.rs | 36 +++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index c624ef729..07e855ab6 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -956,8 +956,8 @@ mod test { let gravity = Vector::y() * -9.81; - let ground_size = 600.0; - let ground_height = 0.1; + let ground_size = 1001.0; + let ground_height = 1.0; /* * Create a flat ground */ @@ -999,7 +999,7 @@ mod test { colliders.insert_with_parent(collider.clone(), character_handle_no_snap, &mut bodies); query_pipeline.update(&colliders); - for i in 0..5200 { + for i in 0..10000 { let mut update_character_controller = |controller: KinematicCharacterController, handle: RigidBodyHandle| { let character_body = bodies.get(handle).unwrap(); @@ -1014,7 +1014,7 @@ mod test { &query_pipeline, character_shape, character_body.position(), - Vector::new(0.1, -0.1, 0.02), + Vector::new(0.1, -0.1, 0.1), filter_character_controller, |collision| collisions.push(collision), ); @@ -1049,5 +1049,33 @@ mod test { &(), ); } + let character_body = bodies.get_mut(character_handle_no_snap).unwrap(); + let translation = character_body.translation(); + + // accumulated numerical errors make the test go less far than it should, + // but it's expected. + assert!( + translation.x >= 997.0, + "actual translation.x:{}", + translation.x + ); + assert!( + translation.z >= 997.0, + "actual translation.z:{}", + translation.z + ); + + let character_body = bodies.get_mut(character_handle_snap).unwrap(); + let translation = character_body.translation(); + assert!( + translation.x >= 997.0, + "actual translation.x:{}", + translation.x + ); + assert!( + translation.z >= 997.0, + "actual translation.z:{}", + translation.z + ); } } From 5421403a1b9ed01359f9351f0279cd988f3385e3 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 9 Sep 2024 16:25:21 +0200 Subject: [PATCH 3/3] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6a110aa8..b2b931fe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. +- Improve ground detection reliability for `KinematicCharacterController`. (#715) ### Modified