基于WebVPython的一维双弹球模拟:橡皮筋效应与动量计算求助
一维小球弹跳与碰撞模拟的问题修复方案
问题根源分析
- 橡皮筋效应与穿透:当前代码使用匀变速运动的解析公式(
y = y0 + v0*t - 0.5*g*t²)更新位置,这种方法假设运动过程中加速度恒定,但碰撞是瞬时速度突变的事件,解析公式无法实时响应碰撞后的状态变化,导致物体穿透、反复触发碰撞逻辑,出现“橡皮筋”式的抖动。 - 碰撞逻辑错误:
- 两球碰撞判断仅比较中心y坐标,未考虑小球半径,实际碰撞发生在两球中心距离等于2倍半径时。
- 碰撞时的速度处理错误,直接对单个球速度反转并乘以恢复系数,未遵循动量守恒与能量守恒,导致碰撞结果不符合物理规律。
核心修复方案
1. 改用数值积分更新运动状态
放弃解析公式,改用逐帧数值积分(欧拉法)更新速度和位置,每帧基于当前速度和加速度计算新位置,这样能及时响应碰撞带来的速度突变:
# 逐帧更新速度和位置 ball.vy += -g * dt ball.pos.y += ball.vy * dt ball2.vy += -g * dt ball2.pos.y += ball2.vy * dt
2. 正确的碰撞检测与动量计算
一维弹性碰撞(带恢复系数c)的速度更新公式:
对于两个质量为m1、m2,碰撞前速度为v1、v2的物体,碰撞后速度v1'、v2'为:
v1' = ((m1 - c*m2)*v1 + (1 + c)*m2*v2) / (m1 + m2) v2' = ((1 + c)*m1*v1 + (m2 - c*m1)*v2) / (m1 + m2)
其中c为恢复系数(0≤c≤1,c=1为完全弹性碰撞,c=0为完全非弹性碰撞)。
碰撞检测需判断两球中心距离是否小于等于2*R,同时要确保两球是相向运动(避免碰撞后重复触发)。
3. 修复地面碰撞逻辑
地面碰撞时,先将小球位置修正到刚好接触地面的位置,再更新速度,避免穿透:
# 地面碰撞修复 if ball.pos.y <= floor.pos.y + R: ball.pos.y = floor.pos.y + R # 修正位置,避免穿透 ball.vy = -sqrt(c) * ball.vy
修改后的完整代码
Web VPython 3.2 # 图表设置 g1 = graph(xtitle="t [s]",ytitle="y [m]", width=500, height=150) fp = gcurve(color=color.green) g2 = graph(xtitle="t [s]",ytitle="v [m/s]", width=500, height=150) fv = gcurve(color=color.red) g3 = graph(xtitle="t [s]",ytitle="E [J]", width=500, height=150) fK = gcurve(color=color.blue) fU = gcurve(color=color.red) fE = gcurve(color=color.magenta) g4 = graph(xtitle="t [s]",ytitle="v [m/s]", width=500, height=150) fyA = gcurve(color=color.green) fyB = gcurve(color=color.orange) # 地面与小球参数 R = 0.02 floor = box(pos = vector(0,-0.005-R,0),size=vector(0.1,0.01,0.1)) h = .3 g = 9.8 c = 0.5 # 恢复系数 t = 0 dt = 0.01 # 小球A初始化 mA = 2 ball = sphere(pos=vector(0, h, 0), radius=R, color=color.green, make_trail=True, trail_type="points", interval=10, retain=10) ball.vy = 0 # 直接给小球添加速度属性 # 小球B初始化 mB = .05 ball2 = sphere(pos=vector(0, h+0.1, 0), radius=R, color=color.orange, make_trail=True, trail_type="points", interval=10, retain=10) ball2.vy = 0 # 主循环 while t < 2: rate(100) # 提高帧率,减少视觉抖动 # 数值积分更新运动状态 ball.vy += -g * dt ball.pos.y += ball.vy * dt ball2.vy += -g * dt ball2.pos.y += ball2.vy * dt # 小球A与地面碰撞 if ball.pos.y <= floor.pos.y + R: ball.pos.y = floor.pos.y + R # 修正位置,避免穿透 ball.vy = -sqrt(c) * ball.vy # 小球B与地面碰撞 if ball2.pos.y <= floor.pos.y + R: ball2.pos.y = floor.pos.y + R ball2.vy = -sqrt(c) * ball2.vy # 两球碰撞检测与处理 distance = abs(ball.pos.y - ball2.pos.y) # 判断是否碰撞,且相向运动(避免碰撞后重复触发) if distance <= 2*R and ((ball.vy > ball2.vy and ball.pos.y < ball2.pos.y) or (ball.vy < ball2.vy and ball.pos.y > ball2.pos.y)): # 临时存储碰撞前速度 v1 = ball.vy v2 = ball2.vy # 应用弹性碰撞公式计算新速度 ball.vy = ((mA - c*mB)*v1 + (1 + c)*mB*v2) / (mA + mB) ball2.vy = ((1 + c)*mA*v1 + (mB - c*mA)*v2) / (mA + mB) # 轻微分离两球,避免连续碰撞 if ball.pos.y < ball2.pos.y: ball.pos.y -= 0.001 ball2.pos.y += 0.001 else: ball.pos.y += 0.001 ball2.pos.y -= 0.001 # 绘制图表 K = 0.5*mA*(ball.vy**2) U = mA*g*ball.pos.y E = K + U fp.plot(t, ball.pos.y) fv.plot(t, ball.vy) fE.plot(t, E) fK.plot(t, K) fU.plot(t, U) fyA.plot(t, ball.vy) fyB.plot(t, ball2.vy) t += dt print("t = ", t , "seconds")
关键修改说明
- 给每个小球直接添加
vy属性,简化状态管理,避免原代码中vy0A、btA等冗余变量。 - 改用数值积分逐帧更新速度和位置,确保碰撞后状态能实时生效。
- 碰撞检测加入半径判断和运动方向判断,避免误触发。
- 碰撞后轻微分离两球,防止连续触发碰撞逻辑。
- 地面碰撞时先修正位置,彻底解决穿透问题。
内容的提问来源于stack exchange,提问作者Amir G
相关产品推荐
相关产品推荐

