Rapier2D零重力场景中弹性球垂直速度归零的原因及解决方法
问题
在零重力环境下,用4个完全弹性壁构建盒子,内部放置完全弹性球,初始速度设为(100., 100.),理论上球应在盒内永久反弹。但实际每次反弹后,速度的y分量会逐渐趋近于零,最终仅在左右壁间往返运动。以下是使用rapier2d和macroquad实现的最小可复现代码:
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 } }
成因
- 默认速度阻尼的影响:Rapier的
IntegrationParameters默认带有linear_damping = 0.01和angular_damping = 0.01配置,该参数会在每帧更新时对刚体速度进行微小衰减。单次衰减幅度虽小,但长期积累后,垂直方向的速度会逐渐被消耗至零。 - 数值精度误差积累:浮点数运算本身存在精度限制,多次碰撞后,速度计算的微小误差会逐步积累,加速垂直方向动能的损失。
修复方案
要实现无能量损失的永久反弹,需要关闭速度阻尼,并确保求解器参数足够减少数值误差:
修改PhysicsWorld的Default实现中的integration_parameters部分,添加关闭阻尼的配置,并设置最小求解器迭代次数与最大次数一致:
let integration_parameters = rapier::IntegrationParameters{ num_solver_iterations: std::num::NonZeroUsize::new(16).unwrap(), min_island_solver_iterations: std::num::NonZeroUsize::new(16).unwrap(), // 确保每次都使用足够的迭代次数 prediction_distance: 0.001, linear_damping: 0.0, // 关闭线性速度阻尼 angular_damping: 0.0, // 关闭角速度阻尼 ..Default::default() };
完整修改后的代码
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(), min_island_solver_iterations: std::num::NonZeroUsize::new(16).unwrap(), prediction_distance: 0.001, linear_damping: 0.0, angular_damping: 0.0, ..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
相关产品推荐
相关产品推荐

