Hi, this is the first time I am posting an issue in this repo. I have encountered something I believe is a minor bug in the way the KinematicCharacterController updates the transformation under certain circumstances. I am using the bevy 3D plugin.
Situation
In the code below demonstrating the problem, we have the following sequence of actions
The "character" spawns some height above the ground and due to gravity falls towards the ground
The character eventually reaches the ground, upon which is rests with the expected offset off from the ground.
After a delay of 2 seconds, a horizontal component is added to the speed of the character.
Together with gravity, which continuously tries to add a vertical component, the desired translation points partially down towards the ground.
This eventually leads to the character moving slightly towards the ground, now with it's "offset layer" overlapping with the ground.
Not shown in this example, this leads to the character intermittently becoming stuck.
The demonstration of the problem is printed via warn messages in the read_result_system function.
Why it matters
As far as I understand from the documentation, and also based on experience from moving the character around, when the offset is too small, the character can inexplicably get stuck.
Example code
use bevy::prelude::*;
use bevy_rapier3d::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())
.add_startup_system(setup_physics)
.add_system(gravity_movement_system)
.add_system_to_stage(CoreStage::PostUpdate, read_result_system)
.add_system_to_stage(CoreStage::PostUpdate, reconcile_character_velocity)
.run();
}
fn setup_physics(mut commands: Commands) {
/* Create the ground. */
commands
.spawn()
.insert(Collider::cuboid(20.0, 20.0, 0.1))
.insert_bundle(TransformBundle::from_transform(Transform::from_xyz(
0.0, 0.0, -0.1,
)));
// Add KinematicCharacterController controlled body with a capsule shape
commands
.spawn()
.insert(RigidBody::KinematicPositionBased)
// Capsule of total height 2.0
.insert(Collider::capsule_z(0.7, 0.3))
.insert(KinematicCharacterController {
offset: CharacterLength::Relative(0.01),
up: Vec3::Z,
..default()
})
.insert(MySpeed::default())
.insert_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 2.0)));
}
/// MySpeed controls the speed of our entity because we are responsible for doing that ourselves with the KinematicCharacterController
#[derive(Debug, Component, Default)]
struct MySpeed(Vec3);
fn gravity_movement_system(
mut query: Query<(&mut MySpeed, &mut KinematicCharacterController)>,
time: Res<Time>,
) {
for (mut speed, mut controller) in &mut query {
speed.0 -= 9.81 * Vec3::Z * time.delta_seconds();
// Start moving after we have hit the ground
if time.seconds_since_startup() > 2.0 {
speed.0.x = 1.0;
}
controller.translation = Some(time.delta_seconds() * speed.0);
}
}
/// Reconcile the actual velocity to that computed by the update step.
///
/// It basically just resets the Up/down (Z) component when it hits the ground.
fn reconcile_character_velocity(
mut controllers: Query<(&mut MySpeed, &KinematicCharacterControllerOutput)>,
time: Res<Time>,
) {
for (mut speed, output) in &mut controllers {
if output.grounded {
speed.0 = output.effective_translation / time.delta_seconds();
}
}
}
fn read_result_system(controllers: Query<(&Transform, &KinematicCharacterControllerOutput)>) {
for (transform, output) in &controllers {
// if less then half of capsule height + offset (1 + 0.02) minus a threshold, warn the user
if transform.translation.z < 1.02 - 1e-5 {
warn!(effective_translation = ?output.effective_translation, desired_translation = ?output.desired_translation, ?transform.translation, "offset is gone")
} else {
info!(effective_translation = ?output.effective_translation, desired_translation = ?output.desired_translation, ?transform.translation, "ok")
}
}
}
The output from this code is something like this while falling and then hitting the ground
2022-11-16T12:47:40.158284Z INFO repotest: ok effective_translation=Vec3(0.0, 0.0, -0.069549195) desired_translation=Vec3(0.0, 0.0, -0.069549195) transform.translation=Vec3(0.0, 0.0, 1.0903841)
2022-11-16T12:47:40.175001Z INFO repotest: ok effective_translation=Vec3(0.0, 0.0, -0.070384145) desired_translation=Vec3(0.0, 0.0, -0.07196561) transform.translation=Vec3(0.0, 0.0, 1.02)
2022-11-16T12:47:40.192101Z INFO repotest: ok effective_translation=Vec3(0.0, 0.0, -7.450581e-9) desired_translation=Vec3(0.0, 0.0, -0.06902031) transform.translation=Vec3(0.0, 0.0, 1.02)
2022-11-16T12:47:40.207368Z INFO repotest: ok effective_translation=Vec3(0.0, 0.0, -7.450581e-9) desired_translation=Vec3(0.0, 0.0, -0.0027409683) transform.translation=Vec3(0.0, 0.0, 1.02)
2022-11-16T12:47:40.231131Z INFO repotest: ok effective_translation=Vec3(0.0, 0.0, -7.450581e-9) desired_translation=Vec3(0.0, 0.0, -0.0025370806) transform.translation=Vec3(0.0, 0.0, 1.02)
Then after a short while when the horizontal component is added to the speed:
As you can see, as soon as the horizontal component is added, it actually starts moving a little bit down towards the ground as well, despite having hovered above the ground by the offset for a while.
Possible solution
I must admit that I just started using rapier and bevy, and I have barely read the source code of the KinematicCharacterController, so I could be wrong in what follows, However, I will give my input on what I think could be wrong, but leave it to you to conclude.
If I understand this correctly, this means that the snap_to_ground method will only be able to snap the character to the ground by attraction, not repulsion, and is thus not able to restore the offset whenever the character gets too close.
Hi, this is the first time I am posting an issue in this repo. I have encountered something I believe is a minor bug in the way the KinematicCharacterController updates the transformation under certain circumstances. I am using the bevy 3D plugin.
Situation
In the code below demonstrating the problem, we have the following sequence of actions
The demonstration of the problem is printed via
warn
messages in theread_result_system
function.Why it matters
As far as I understand from the documentation, and also based on experience from moving the character around, when the offset is too small, the character can inexplicably get stuck.
Example code
The output from this code is something like this while falling and then hitting the ground
Then after a short while when the horizontal component is added to the speed:
As you can see, as soon as the horizontal component is added, it actually starts moving a little bit down towards the ground as well, despite having hovered above the ground by the offset for a while.
Possible solution
I must admit that I just started using rapier and bevy, and I have barely read the source code of the KinematicCharacterController, so I could be wrong in what follows, However, I will give my input on what I think could be wrong, but leave it to you to conclude.
If for some reason the shape of the character is closer to the ground then the offset (perhaps due to rounding errors or a consequence of previous computations), https://github.com/dimforge/rapier/blob/c600549aacbde1361eba862b34a23f63d806d6a9/src/control/character_controller.rs#L342 will be a no-op due to
offset
being larger thanhit.toi
, so(hit.toi - offset).max(0.0)
will be zero.If I understand this correctly, this means that the
snap_to_ground
method will only be able to snap the character to the ground by attraction, not repulsion, and is thus not able to restore the offset whenever the character gets too close.Environment