vpython数值模拟系列(2. 盒子中反弹的球)

        笔者学习VPython的时候曾遇到了不少困难,所以在此撰写系列教程与案例,希望能对学习VPython提供相关借鉴。如有任何错误或疑问,欢迎评论指出!

        本案例将实现盒子中反弹的球,以下为详细教程:

1.导入VPython库

from vpython import *

2.相机的位置与方位

在场景窗口中,同时按住鼠标左右按键,上下移动鼠标(或者滚动鼠标中键)可以进行缩放场景;按住鼠标右键移动鼠标可以旋转场景。进行这些操作时,场景中的物体并没有改变,只是我们观察物体的方位改变了。我们观察3D场景的工具就是摄像机,我们可以定义摄像机的相关参数:

scene.center 摄像机持续注视的位置。默认为< 0,0,0 >。

scene.forward 指向相机所指向方向的矢量。默认为< 0,0,1 >,向量的大小被忽略。如果scene.forward改变, 摄像机的位置会移动到使scene.forward指向scene.center的位置。

scene.up 垂直于scene.forward的矢量,默认为<0,1,0>。

scene.camera.pos 摄像机的位置。当 scene.autoscaleTrue摄像机的位置会自动改变。

scene.camera.axis 摄像机指向的当前方向。并且满足
scene.camera.axis = scene.center - scene.camera.pos

为了明白照相机的位置和坐标轴之间的关系,可以运行以下程序:

from vpython import *
display = canvas(width=300, height=300)
# 建立一个空间直角坐标系 —— 右手系(RGB对应XYZ)
arrow_x = arrow(pos=vector(1,0,0), axis=vector(1,0,0), color=color.red)
arrow_y = arrow(pos=vector(0,1,0), axis=vector(0,1,0), color=color.green)
arrow_z = arrow(pos=vector(0,0,1), axis=vector(0,0,1), color=color.blue)

通过该图我们可以知道,窗口的中心为坐标原点,x轴为从左到右,y轴为从下到上,z轴为从屏幕里到屏幕外,此时的照相机位于z轴正方向上的某点,方向沿着z轴负方向俯视。为方便显示,固下文的重力加速度g设为vector(0, 9.8, 0)。(笔者对摄像头的方向进行了大量调整,未能使初始坐标系变成习惯的角度,故出此下策,将绿色的轴视为z轴)

3.初始变量与画布设置

# 一些初始值的设定
t, dt, r, d = 0, 0.005, 0.1, 6
g = vector(0, 9.8, 0)  # 重力加速度
f = 0.9  # 反弹能量系数,1.0表示完全反弹
display = canvas(width=500, height=500,  background=color.white)

4.墙体与墙面的构建
这里墙体的构建使用了vpython中的box类型,由于其厚度不能取零,固取一较小值,在实际模拟的物理过程中并未考虑该厚度。

# 创建球体和6个墙面,墙面设置为半透明,以观察球体的运动轨迹
ball = sphere(pos=vector(-5, 0, 0), radius=0.5, color=color.cyan)
wall_right = box(pos=vector(6, 0, 0), size=vector(0.1, 2*d, 2*d), color=color.cyan, opacity=0.02)
wall_left = box(pos=vector(-6, 0, 0), size=vector(0.1, 2*d, 2*d), color=color.cyan, opacity=0.02)
wall_front = box(pos=vector(0, -6, 0), size=vector(2*d, 0.1, 12), color=color.cyan, opacity=0.02)
wall_back = box(pos=vector(0, 6, 0), size=vector(2*d, 0.1, 2*d), color=color.cyan, opacity=0.02)
wall_bottom = box(pos=vector(0, 0, -6), size=vector(2*d, 2*d, 0.1), color=color.cyan, opacity=0.02)
wall_top = box(pos=vector(0, 0, 6), size=vector(2*d, 2*d, 0.1), color=color.cyan, opacity=0.02)

为使可视化更加逼真,还可增加墙面的棱线,增强容器的立体效果。

# 墙面的棱线
bottom = curve(color=color.cyan, radius=r, opacity=0.03)
bottom.append([vector(-d, -d, -d), vector(-d, -d, d), vector(d, -d, d), vector(d, -d, -d), vector(-d, -d, -d)],
              opacity=0.03)
top = curve(color=color.cyan, radius=r)
top.append([vector(-d, d, -d), vector(-d, d, d), vector(d, d, d), vector(d, d, -d), vector(-d, d, -d)],
           opacity=0.03)
vert1 = curve(color=color.cyan, radius=r, opacity=0.03)
vert2 = curve(color=color.cyan, radius=r, opacity=0.03)
vert3 = curve(color=color.cyan, radius=r, opacity=0.03)
vert4 = curve(color=color.cyan, radius=r, opacity=0.03)
vert1.append([vector(-d, -d, -d), vector(-d, d, -d)], opacity=0.03)
vert2.append([vector(-d, -d, d), vector(-d, d, d)], opacity=0.03)
vert3.append([vector(d, -d, d), vector(d, d, d)], opacity=0.03)
vert4.append([vector(d, -d, -d), vector(d, d, -d)], opacity=0.03)

这里构建棱线的思路是使用curve类型:先将顶部和底部的两个正方形画出,然后画出四条垂直的棱线,则可组成正方体的12条棱线。

5.速度与轨迹的处理

为了使可视化更明显,我们添加一个箭头显示小球的速度方向,箭头的长短表示小球的速度大小。其中第二段代码是写在while循环之中,在循环中不断更新箭头的方向,长短。

ball.velocity = vector(8, 6, 12)
bv = arrow(pos=ball.pos, axis=ball.velocity*0.2, color=color.yellow)
ball.trail = curve(color=vector(1, 0, 0))
    # 更新速度箭头的位置和方向
    bv.pos = ball.pos
    bv.axis = ball.velocity*0.2
    ball.trail.append(pos=ball.pos, color=vector(1, 0, 0))

 6.更新小球位置与速度

while t < 30:
    rate(1/dt)
    t += dt
    # 重力加速度改变z轴方向的速度,不存在反弹时修改速度
    ball.velocity -= g * dt
    # 根据速度修改球体的位置
    ball.pos += ball.velocity * dt

7.墙面碰撞的处理

处理球体和墙壁的碰撞,x, y, z三个方向的碰撞处理方式相同。这里以x方向为例简要说明一下碰撞处理。当球体的x轴方向的速度为正时,判断球体是否和正方向的墙壁(右墙)相撞,如果相撞的话则将其x轴方向的速度反向,并且乘以碰撞系数模拟能量损失。当球体的x轴方向速度为负时,和左墙进行碰撞检测,重复上述类似操作,对于剩下的y轴和z轴也是如此。

    # 速度为正时判断正方向的墙,速度为负时判断负方向的墙
    # 处理反弹时需要修正球的位置,使它正好和墙面接触

    # 处理左右墙的反弹
    if ball.velocity.x * ball.pos.x > 0 and ball.pos.x >= wall_right.pos.x - ball.radius:
        ball.velocity.x *= -f
    if ball.velocity.x * ball.pos.x > 0 and ball.pos.x <= wall_left.pos.x + ball.radius:
        ball.velocity.x *= -f

    # 处理前后墙的反弹
    if ball.velocity.y * ball.pos.y > 0 and ball.pos.y >= wall_back.pos.y - ball.radius:
        ball.velocity.y *= -f
    if ball.velocity.y * ball.pos.y > 0 and ball.pos.y <= wall_front.pos.y + ball.radius:
        ball.velocity.y *= -f

    # 处理上下墙的反弹
    if ball.velocity.z * ball.pos.z > 0 and ball.pos.z >= wall_top.pos.z - ball.radius:
        ball.velocity.z *= -f
    elif ball.velocity.z * ball.pos.z > 0 and ball.pos.z <= wall_bottom.pos.z + ball.radius:
        ball.velocity.z *= -f

按照以上思路,即可实现盒子中反弹的球的案例,以下为源代码:

from vpython import *
# 一些初始值的设定
t, dt, r, d = 0, 0.005, 0.1, 6
g = vector(0, 9.8, 0)  # 重力加速度
f = 0.9  # 反弹能量系数,1.0表示完全反弹
display = canvas(width=500, height=500,  background=color.white)

# 创建球体和6个墙面,墙面设置为半透明,以观察球体的运动轨迹
ball = sphere(pos=vector(-5, 0, 0), radius=0.5, color=color.cyan)
wall_right = box(pos=vector(d, 0, 0), size=vector(0.1, 2*d, 2*d), color=color.cyan, opacity=0.02)
wall_left = box(pos=vector(-6, 0, 0), size=vector(0.1, 2*d, 2*d), color=color.cyan, opacity=0.02)
wall_front = box(pos=vector(0, -6, 0), size=vector(2*d, 0.1, 12), color=color.cyan, opacity=0.02)
wall_back = box(pos=vector(0, 6, 0), size=vector(2*d, 0.1, 2*d), color=color.cyan, opacity=0.02)
wall_bottom = box(pos=vector(0, 0, -6), size=vector(2*d, 2*d, 0.1), color=color.cyan, opacity=0.02)
wall_top = box(pos=vector(0, 0, 6), size=vector(2*d, 2*d, 0.1), color=color.cyan, opacity=0.02)

# 墙面的棱线
bottom = curve(color=color.cyan, radius=r, opacity=0.03)
bottom.append([vector(-d, -d, -d), vector(-d, -d, d), vector(d, -d, d), vector(d, -d, -d), vector(-d, -d, -d)],
              opacity=0.03)
top = curve(color=color.cyan, radius=r)
top.append([vector(-d, d, -d), vector(-d, d, d), vector(d, d, d), vector(d, d, -d), vector(-d, d, -d)],
           opacity=0.03)
vert1 = curve(color=color.cyan, radius=r, opacity=0.03)
vert2 = curve(color=color.cyan, radius=r, opacity=0.03)
vert3 = curve(color=color.cyan, radius=r, opacity=0.03)
vert4 = curve(color=color.cyan, radius=r, opacity=0.03)
vert1.append([vector(-d, -d, -d), vector(-d, d, -d)], opacity=0.03)
vert2.append([vector(-d, -d, d), vector(-d, d, d)], opacity=0.03)
vert3.append([vector(d, -d, d), vector(d, d, d)], opacity=0.03)
vert4.append([vector(d, -d, -d), vector(d, d, -d)], opacity=0.03)

ball.velocity = vector(8, 6, 12)
bv = arrow(pos=ball.pos, axis=ball.velocity*0.2, color=color.yellow)
ball.trail = curve(color=vector(1, 0, 0))


while t < 30:
    rate(1/dt)
    t += dt
    # 重力加速度改变z轴方向的速度,不存在反弹时修改速度
    ball.velocity -= g * dt
    # 根据速度修改球体的位置
    ball.pos += ball.velocity * dt

    # 速度为正时判断正方向的墙,速度为负时判断负方向的墙
    # 处理反弹时需要修正球的位置,使它正好和墙面接触

    # 处理左右墙的反弹
    if ball.velocity.x * ball.pos.x > 0 and ball.pos.x >= wall_right.pos.x - ball.radius:
        ball.velocity.x *= -f
    if ball.velocity.x * ball.pos.x > 0 and ball.pos.x <= wall_left.pos.x + ball.radius:
        ball.velocity.x *= -f

    # 处理前后墙的反弹
    if ball.velocity.y * ball.pos.y > 0 and ball.pos.y >= wall_back.pos.y - ball.radius:
        ball.velocity.y *= -f
    if ball.velocity.y * ball.pos.y > 0 and ball.pos.y <= wall_front.pos.y + ball.radius:
        ball.velocity.y *= -f

    # 处理上下墙的反弹
    if ball.velocity.z * ball.pos.z > 0 and ball.pos.z >= wall_top.pos.z - ball.radius:
        ball.velocity.z *= -f
    elif ball.velocity.z * ball.pos.z > 0 and ball.pos.z <= wall_bottom.pos.z + ball.radius:
        ball.velocity.z *= -f

    # 更新速度箭头的位置和方向
    bv.pos = ball.pos
    bv.axis = ball.velocity*0.2
    ball.trail.append(pos=ball.pos, color=vector(1, 0, 0))

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值