bevy_rapier
bevy_rapier copied to clipboard
Changing `substep` leads to weird behavior
I have a small project with a ball inside 4 walls. All parameters are set in such way so ball should move indefinitely. It works properly when substep
is set to 1, but as soon I increase it ball starts to slow down and change direction after a collision incorrectly.
Repro:
use bevy::{prelude::*, render::camera::ScalingMode, sprite::MaterialMesh2dBundle};
use bevy_rapier2d::prelude::*;
const PIXELS_PER_METER: f32 = 30.;
const TICKS_PER_SECOND: f32 = 100.;
const BALL_STARTING_POSITION: Vec3 = Vec3::new(0.0, 0.0, 1.0);
const BALL_SIZE: Vec3 = Vec3::new(1.0, 1.0, 0.0);
const BALL_SPEED: f32 = 150.0;
const INITIAL_BALL_DIRECTION: Vec2 = Vec2::new(0.5, -0.5);
const WALL_THICKNESS: f32 = 0.5;
const LEFT_WALL: f32 = -15.;
const RIGHT_WALL: f32 = 15.;
const BOTTOM_WALL: f32 = -10.;
const TOP_WALL: f32 = 10.;
const BACKGROUND_COLOR: Color = Color::rgb(0.9, 0.9, 0.9);
const BALL_COLOR: Color = Color::rgb(1.0, 0.5, 0.5);
const WALL_COLOR: Color = Color::rgb(0.8, 0.8, 0.8);
#[derive(Component)]
struct Ball;
#[derive(Bundle)]
struct WallBundle {
sprite_bundle: SpriteBundle,
collider: Collider,
friction: Friction,
restitution: Restitution,
rigid_body: RigidBody,
ccd: Ccd,
location: WallLocation,
}
#[derive(Component, Eq, PartialEq)]
enum WallLocation {
Left,
Right,
Bottom,
Top,
}
impl WallLocation {
fn position(&self) -> Vec2 {
match self {
WallLocation::Left => Vec2::new(LEFT_WALL, 0.),
WallLocation::Right => Vec2::new(RIGHT_WALL, 0.),
WallLocation::Bottom => Vec2::new(0., BOTTOM_WALL),
WallLocation::Top => Vec2::new(0., TOP_WALL),
}
}
fn size(&self) -> Vec2 {
let arena_height = TOP_WALL - BOTTOM_WALL;
let arena_width = RIGHT_WALL - LEFT_WALL;
assert!(arena_height > 0.0);
assert!(arena_width > 0.0);
match self {
WallLocation::Left | WallLocation::Right => {
Vec2::new(WALL_THICKNESS, arena_height + WALL_THICKNESS)
}
WallLocation::Bottom | WallLocation::Top => {
Vec2::new(arena_width + WALL_THICKNESS, WALL_THICKNESS)
}
}
}
}
impl WallBundle {
fn new(location: WallLocation) -> WallBundle {
WallBundle {
sprite_bundle: SpriteBundle {
transform: Transform {
translation: location.position().extend(0.0) * PIXELS_PER_METER,
scale: location.size().extend(1.0) * PIXELS_PER_METER,
..default()
},
sprite: Sprite {
color: WALL_COLOR,
..default()
},
..default()
},
rigid_body: RigidBody::Fixed,
ccd: Ccd::enabled(),
collider: Collider::cuboid(0.5, 0.5),
friction: Friction::new(0.0),
restitution: Restitution::new(1.0),
location,
}
}
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(RapierPhysicsPlugin::<NoUserData>::pixels_per_meter(
PIXELS_PER_METER,
))
.insert_resource(RapierConfiguration {
timestep_mode: TimestepMode::Interpolated {
dt: 1. / TICKS_PER_SECOND,
time_scale: 1.0,
substeps: 10,
},
gravity: Vec2::ZERO,
..default()
})
.add_event::<CollisionEvent>()
.insert_resource(ClearColor(BACKGROUND_COLOR))
.add_systems(Startup, setup)
.add_systems(Update, bevy::window::close_on_esc)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2dBundle {
projection: OrthographicProjection {
near: -1000.,
far: 1000.,
scaling_mode: ScalingMode::AutoMin {
min_width: 1024.,
min_height: 768.,
},
..default()
},
..default()
});
commands.spawn((
MaterialMesh2dBundle {
mesh: meshes
.add(shape::Circle::new(BALL_SIZE.x / 2.0 * PIXELS_PER_METER).into())
.into(),
material: materials.add(ColorMaterial::from(BALL_COLOR)),
transform: Transform::from_translation(BALL_STARTING_POSITION),
..default()
},
RigidBody::Dynamic,
Ccd::enabled(),
LockedAxes::ROTATION_LOCKED,
Collider::ball(BALL_SIZE.x / 2.0 * PIXELS_PER_METER),
Damping {
angular_damping: 0.0,
linear_damping: 0.0,
},
GravityScale(0.),
Friction::new(0.0),
Restitution::new(1.0),
Ball,
Velocity {
linvel: (INITIAL_BALL_DIRECTION.normalize() * BALL_SPEED),
angvel: 0.,
},
));
commands.spawn(WallBundle::new(WallLocation::Left));
commands.spawn(WallBundle::new(WallLocation::Right));
commands.spawn(WallBundle::new(WallLocation::Bottom));
commands.spawn(WallBundle::new(WallLocation::Top));
}