dimforge / bevy_rapier

Official Rapier plugin for the Bevy game engine.
https://rapier.rs
Apache License 2.0
1.2k stars 257 forks source link

[Bug]: Incorrect normals when ray-casting using `cast_shape` #372

Open AntonPieper opened 1 year ago

AntonPieper commented 1 year ago

I am trying to write a character controller using rapier. To control when I can jump, I want to use cast_shape to check whether I am currently grounded. This leads to a problem: the resulting normal is always Vec2(-0.0, 1.0) wven if I hit walls.

The relevant code of the system:

fn jump_controller(
    mut players: Query<(Entity, &mut Velocity, &Transform, &Collider), With<PlayerTag>>,
    keys: Res<Input<KeyCode>>,
    physics: Res<RapierContext>,
) {
    for (entity, mut velocity, transform, collider) in &mut players {
        let grounded = physics
            .cast_shape(
                transform.translation.truncate(),
                0.,
                Vec2::NEG_Y,
                collider,
                0.01,
                QueryFilter::new().exclude_collider(entity),
            )
            .is_some_and(|(_, collisions)| {
                println!("{:?}", collisions);
                (0.9..1.1).contains(&collisions.normal2.dot(Vec2::NEG_Y))
            });
        let mut movement = Vec2::default();
        if keys.pressed(KeyCode::Space) && grounded {
            movement.y += 200.;
        }
        velocity.linvel += movement;
    }
}
Full code (main.rs) Here is my example code (important parts at lines 101 - 114 in the `jump_controller` function): ```rs use bevy::prelude::*; use bevy_rapier2d::prelude::*; use rand::prelude::*; #[derive(Bundle)] struct Enemy { #[bundle] sprite: SpriteBundle, collider: Collider, } #[derive(Component)] struct PlayerTag; #[derive(Component)] struct EnemyTag; const START_X: i32 = -10; const START_Y: i32 = 5; const END_Y: i32 = 20; const END_X: i32 = 10; fn create_player(mut commands: Commands) { commands .spawn(RigidBody::Dynamic) .insert(Collider::cuboid(5., 5.)) .insert(SpriteBundle { sprite: Sprite { color: Color::rgb(0.25, 0.25, 0.75), custom_size: Some(Vec2::new(10., 10.)), ..default() }, ..default() }) .insert(Velocity::default()) .insert(LockedAxes::ROTATION_LOCKED) .insert(GravityScale(1.)) .insert(Ccd::enabled()) .insert(PlayerTag {}); } fn create_camera(mut commands: Commands) { commands.spawn(Camera2dBundle::default()); } fn create_enemies(mut commands: Commands) { for y in START_Y..END_Y { for x in START_X..END_X { commands .spawn(SpriteBundle { transform: Transform::from_translation(Vec3::new( x as f32 * 21., y as f32 * 21., 0., )) .with_scale(Vec3::new(20., 20., 1.)), sprite: Sprite { color: Color::hsl(rand::thread_rng().gen_range(0. ..=360.), 1., 0.5), ..default() }, ..default() }) .insert(Collider::cuboid(0.5, 0.5)) .insert(EnemyTag {}); } } } fn create_ground(mut commands: Commands) { commands .spawn(SpriteBundle { sprite: Sprite { color: Color::Rgba { red: 0.5, green: 0.5, blue: 0.5, alpha: 1., }, ..default() }, transform: Transform::from_translation(Vec3::new(0., -100., 0.)) .with_scale(Vec3::new(1000., 100., 1.)), ..default() }) .insert(Collider::cuboid(0.5, 0.5)); } fn walk_controller(mut players: Query<&mut Velocity, With>, keys: Res>) { for mut velocity in &mut players { let mut movement = Vec2::default(); movement.x -= if keys.pressed(KeyCode::A) { 1. } else { 0. }; movement.x += if keys.pressed(KeyCode::D) { 1. } else { 0. }; movement = movement.normalize_or_zero(); velocity.linvel += movement; } } fn jump_controller( mut players: Query<(Entity, &mut Velocity, &Transform, &Collider), With>, keys: Res>, physics: Res, ) { for (entity, mut velocity, transform, collider) in &mut players { let grounded = physics .cast_shape( transform.translation.truncate(), 0., Vec2::NEG_Y, collider, 0.01, QueryFilter::new().exclude_collider(entity), ) .is_some_and(|(_, collisions)| { println!("{:?}", collisions); (0.9..1.1).contains(&collisions.normal2.dot(Vec2::NEG_Y)) }); let mut movement = Vec2::default(); if keys.pressed(KeyCode::Space) && grounded { movement.y += 200.; } velocity.linvel += movement; } } fn camera_controller( mut query: Query<&mut Transform, (With, Without)>, player: Query<&Transform, With>, ) { for mut camera_transform in &mut query { camera_transform.translation = player.single().translation; } } pub struct GamePlugin; impl Plugin for GamePlugin { fn build(&self, app: &mut App) { app.add_startup_systems((create_enemies, create_player, create_camera, create_ground)) .add_systems(( walk_controller, jump_controller, camera_controller.after(jump_controller), )); } } fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(RapierPhysicsPlugin::::pixels_per_meter(100.0)) .add_plugin(RapierDebugRenderPlugin::default()) .add_plugin(GamePlugin) .run(); } ```
My Cargo config **Cargo.toml** ```toml [package] name = "animation-programming" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bevy = { version = "0.10.1" } rand = "0.8.5" bevy_rapier2d = { version = "0.21.0", features = [ "simd-nightly", "debug-render-2d" ] } # Enable a small amount of optimization in debug mode [profile.dev] # opt-level = 1 [profile.release] lto = "thin" # Enable high optimizations for dependencies (incl. Bevy), but not for our code: [profile.dev.package."*"] opt-level = 3 ``` **rust-toolchain.toml** ```toml [toolchain] channel = "nightly" ```
Aceeri commented 1 year ago

Be careful to check the toi.status for TOIStatus::Penetrating here, the results are currently undefined if it is. Ideally I think we hide the Toi stuff behind an enum so you have to check the status, but that's on my TODO list currently.

Aceeri commented 11 months ago

Also be careful to check the normal1 vs normal2 of the Toi, I'm not entirely sure if normal2 is actually the hit collider.

Vrixyz commented 2 months ago

@AntonPieper Is that still an issue for you with the information provided by Aceeri ?

AntonPieper commented 1 month ago

I have moved on since then and haven't yet started another project again using bevy, so I don't know.

I can check this again this weekend though. Thanks for the valuable information!