dimforge / dimforge/bevy_rapier
[Bug]: Incorrect normals when ray-casting using `cast_shape`
- Dominant language
- Rust
- Stars
- 1.6k
- Forks
- 282
- PR merge metrics
- No merged PRs in 30d
Description
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:
```rs
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;
}
}
```
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"
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.