bevy_rapier icon indicating copy to clipboard operation
bevy_rapier copied to clipboard

Changing `substep` leads to weird behavior

Open roman-holovin opened this issue 1 year ago • 0 comments

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));
}

roman-holovin avatar Feb 11 '24 02:02 roman-holovin