Why does the vertical velocity tend towards zero in this simple

ghz 8months ago ⋅ 124 views

Why does the vertical velocity tend towards zero in this simple example?

I have 4 perfectly elastic walls arranged as a box and a perfectly elastic ball in zero gravity. The ball is given an initial velocity of (100., 100.) and should bounce between the four borders of the box forever.

However, for some unknown reason, the y component of the velocity tends toward zero after each bounce, until eventually, it is just bouncing off the left and right walls with no vertical velocity at all.

Here is a minimal working example of my code:

use rapier2d::prelude as rapier;
use macroquad::prelude as mq;

struct PhysicsWorld {
    gravity: rapier::Vector<f32>,
    integration_parameters: rapier::IntegrationParameters,
    pipeline: rapier::PhysicsPipeline,
    island_manager: rapier::IslandManager,
    broad_phase: rapier::BroadPhase,
    narrow_phase: rapier::NarrowPhase,
    rigid_body_set: rapier::RigidBodySet,
    collider_set: rapier::ColliderSet,
    impulse_joint_set: rapier::ImpulseJointSet,
    multibody_joint_set: rapier::MultibodyJointSet,
    ccd_solver: rapier::CCDSolver,
    query_pipeline: rapier::QueryPipeline,
    physics_hooks: (),
    event_handler: (),
}

impl Default for PhysicsWorld {
    fn default() -> Self {
        let gravity = rapier::Vector::zeros();
        let integration_parameters = rapier::IntegrationParameters{
            num_solver_iterations: std::num::NonZeroUsize::new(16).unwrap(),
            prediction_distance: 0.001,
            ..Default::default()
        
        };
        let pipeline = rapier::PhysicsPipeline::new();
        let island_manager = rapier::IslandManager::new();
        let broad_phase = rapier::BroadPhase::new();
        let narrow_phase = rapier::NarrowPhase::new();
        let rigid_body_set = rapier::RigidBodySet::new();
        let collider_set = rapier::ColliderSet::new();
        let impulse_joint_set = rapier::ImpulseJointSet::new();
        let multibody_joint_set = rapier::MultibodyJointSet::new();
        let ccd_solver = rapier::CCDSolver::new();
        let query_pipeline = rapier::QueryPipeline::new();
        let physics_hooks = ();
        let event_handler = ();
        Self {
            gravity,
            integration_parameters,
            pipeline,
            island_manager,
            broad_phase,
            narrow_phase,
            rigid_body_set,
            collider_set,
            impulse_joint_set,
            multibody_joint_set,
            ccd_solver,
            query_pipeline,
            physics_hooks,
            event_handler,
        }
    }
}

impl PhysicsWorld {
    fn update(&mut self) {
        self.pipeline.step(
            &self.gravity,
            &self.integration_parameters,
            &mut self.island_manager,
            &mut self.broad_phase,
            &mut self.narrow_phase,
            &mut self.rigid_body_set,
            &mut self.collider_set,
            &mut self.impulse_joint_set,
            &mut self.multibody_joint_set,
            &mut self.ccd_solver,
            Some(&mut self.query_pipeline),
            &self.physics_hooks,
            &self.event_handler,
        );
    }

    pub fn add_ball_with_velocity(&mut self, radius: f32, x: f32, y: f32, vx: f32, vy: f32) -> (rapier::RigidBodyHandle, rapier::ColliderHandle) {
        let rigid_body = rapier::RigidBodyBuilder::dynamic().translation(rapier::vector![x, y]).linvel(rapier::vector![vx, vy]).build();
        let collider = rapier::ColliderBuilder::ball(radius).restitution(1.0).build();        
        let ball_r_handle = self.rigid_body_set.insert(rigid_body);
        let ball_c_handle = self.collider_set.insert_with_parent(collider, ball_r_handle, &mut self.rigid_body_set);
        return (ball_r_handle, ball_c_handle);
    }

    pub fn add_fixed_cuboid(&mut self, x: f32, y: f32, hx: f32, hy: f32) -> (rapier::RigidBodyHandle, rapier::ColliderHandle) {
        let rigid_body = rapier::RigidBodyBuilder::fixed().translation(rapier::vector![x, y]).build();
        let collider = rapier::ColliderBuilder::cuboid(hx, hy).restitution(1.0).build();
        let cuboid_r_handle = self.rigid_body_set.insert(rigid_body);
        let cuboid_c_handle = self.collider_set.insert_with_parent(collider, cuboid_r_handle, &mut self.rigid_body_set);
        return (cuboid_r_handle, cuboid_c_handle);
    }

    pub fn add_window_borders(&mut self) {
        let width = mq::screen_width();
        let height = mq::screen_height();
        let thickness = 10.0;
        let h_center_x = width / 2.0;
        let v_center_y = height / 2.0;
        let h_top_y = height - thickness / 2.0; //(center of top border)
        let h_bottom_y = thickness / 2.0; //(center of bottom border)
        let v_left_x = thickness / 2.0; //(center of left border)
        let v_right_x = width - thickness / 2.0; //(center of right border)
        let _top = self.add_fixed_cuboid(h_center_x, h_top_y, width / 2.0, thickness / 2.0);
        let _bottom = self.add_fixed_cuboid(h_center_x, h_bottom_y, width / 2.0, thickness / 2.0);
        let _left = self.add_fixed_cuboid(v_left_x, v_center_y, thickness / 2.0, height / 2.0);
        let _right = self.add_fixed_cuboid(v_right_x, v_center_y, thickness / 2.0, height / 2.0);
    }

    //draw the colliders (using the rigid body they are attached to)
    pub fn draw_colliders(&self) {
        for (_handle, collider) in self.collider_set.iter() {
            let rigid_body_handle = collider.parent().expect("Collider has no parent");
            let body = self.rigid_body_set.get(rigid_body_handle).unwrap();
            let pos = body.position().translation.vector;
            let shape: &dyn rapier::Shape = collider.shape();
            match (shape.as_ball(), shape.as_cuboid()) {
                (Some(ball), None) => {
                    mq::draw_circle(pos.x, pos.y, ball.radius, mq::WHITE);
                }
                (None, Some(cuboid)) => {
                    mq::draw_rectangle(pos.x - cuboid.half_extents.x, pos.y - cuboid.half_extents.y, cuboid.half_extents.x * 2.0, cuboid.half_extents.y * 2.0, mq::WHITE);
                }
                _ => {unreachable!()}
            }
        }
    }

    pub fn draw_ball_velocity_vector(&self, handle: rapier::RigidBodyHandle) {
        let body = self.rigid_body_set.get(handle).unwrap();
        let vel = body.linvel();
        mq::draw_text(&format!("vx: {:.2}, vy: {:.2}", vel.x, vel.y), 10., 30., 20.0, mq::WHITE);
    }
}

const ASPECT_RATIO: f32 = 16.0 / 9.0;
const WINDOW_WIDTH: f32 = 300.0;
const WINDOW_HEIGHT: f32 = WINDOW_WIDTH * ASPECT_RATIO;

fn main() {
    macroquad::Window::from_config(
        mq::Conf {
            window_width: WINDOW_WIDTH as i32,
            window_height: WINDOW_HEIGHT as i32,
            ..Default::default()
        },
        amain(),
    );
}


async fn amain() {
    let mut world = PhysicsWorld::default();
    let (ball, _) = world.add_ball_with_velocity(10.0, 200.0, 200.0, 100.0, 100.0);
    world.add_window_borders();
    loop {
        world.update();
        mq::clear_background(mq::BLACK);   
        world.draw_ball_velocity_vector(ball);     
        world.draw_colliders();
        mq::next_frame().await
    }
}

Why does this happen? What is causing the ball to lose the y component of its velocity? How can I fix it?

Answers

The issue you're encountering with the ball losing its y-component velocity after each bounce is likely due to how the ball is interacting with the boundaries of the box. Specifically, when the ball collides with the top or bottom walls, its y-component velocity should be reversed to simulate a perfectly elastic collision. However, if the collision response is not properly handled, the y-component velocity may not be reversed correctly, leading to a gradual loss of vertical velocity over time.

Here are a few potential reasons and solutions for this issue:

  1. Collision Response: Ensure that the collision response between the ball and the box boundaries is correctly implemented to reverse the velocity component perpendicular to the wall upon collision. Check your collision detection and resolution logic to make sure it properly accounts for the direction of the collision and reverses the velocity accordingly.

  2. Restitution Coefficient: The coefficient of restitution determines the elasticity of collisions. Ensure that the coefficient of restitution is set to 1.0 for perfectly elastic collisions, both for the ball and the box boundaries. In your code, you've already set the restitution coefficient to 1.0 when creating the colliders, which is correct.

  3. Collision Detection: Verify that the collision detection mechanism detects collisions between the ball and the box boundaries accurately. Ensure that the ball's position and velocity are updated correctly after each collision to prevent it from penetrating the boundaries.

  4. Debugging: Use debugging techniques to inspect the ball's velocity and position before and after collisions. Print debug information or visualize the collision interactions to identify any discrepancies or errors in the collision response.

  5. Integration Parameters: Double-check the integration parameters used in your physics simulation, particularly the prediction_distance, which affects the accuracy of collision detection and resolution. Adjusting this parameter may improve the accuracy of collision handling in your simulation.

By carefully reviewing and debugging your collision detection and response logic, ensuring correct restitution coefficients, and verifying the accuracy of collision handling mechanisms, you should be able to fix the issue of the ball losing its y-component velocity after each bounce in your simulation.