文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

使用python怎么模拟在天空中放风筝

2023-06-14 18:17

关注

使用python怎么模拟在天空中放风筝?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

python可以做什么

Python是一种编程语言,内置了许多有效的工具,Python几乎无所不能,该语言通俗易懂、容易入门、功能强大,在许多领域中都有广泛的应用,例如最热门的大数据分析,人工智能,Web开发等。

pip install numpypip install scipypip install pillowpip install wxgl

NumPy和pillow是Python旗下最常用的科学计算库和图像处理库,属于常用模块。WxGL是一个基于PyOpenGL的三维数据可视化库,以wx为显示后端,提供Matplotlib风格的交互式应用模式,同时,也可以和wxPython无缝结合,在wx的窗体上绘制三维模型。关于WxGL的更多信息,请参阅我的另一篇博客《十分钟玩转3D绘图:WxGL完全手册》。

2.2 草原和风筝素材

请下载下面的草原和风筝素材,保存到项目路径下的res文件夹中。如果使用其他图片,请保持草原图片的宽高比为4:3,风筝素材需要带透明通道的png格式。
草原素材:sky.jpg

使用python怎么模拟在天空中放风筝

风筝素材:butterfly.jpg

使用python怎么模拟在天空中放风筝

风筝素材:eagle.jpg

使用python怎么模拟在天空中放风筝

风筝素材:fish.jpg

使用python怎么模拟在天空中放风筝

2.3 打开IDLE,导入模块

>>> import numpy as np>>> from PIL import Image>>> import wxgl.wxplot as plt # 交互式3D绘图库>>> from scipy.spatial.transform import Rotation # 空间旋转计算

3 制作工序

3.1 蓝天和草原

用3D绘制天空,最常用的方法是天空顶和天空盒。不过,这两个方法都有局限性,效果只能说差强人意。我们这里用的是天空盒。所谓天空盒,顾名思义,就是从一张图片上裁切出六个矩形,拼成一个六面体,观察者站在六面体内,就有了“天苍苍野茫茫”的赶脚。

使用python怎么模拟在天空中放风筝

下图是从上图裁切出的上下前后左右六个面。

使用python怎么模拟在天空中放风筝

了解了天空盒的原理,实现起来就简单多了。先来裁切上下前后左右六个面。

>>> im = np.array(Image.open(r'D:\temp\kite\res\sky.jpg')) # 打开蓝天草原的图片>>> u = im.shape[0]//3 # 天空盒(正六面体的棱长)>>> im_top = im[:u, u:2*u, :]>>> im_left = im[u:2*u, :u, :]>>> im_front = im[u:2*u, u:2*u, :]>>> im_right = im[u:2*u, 2*u:3*u, :]>>> im_back = im[u:2*u, 3*u:, :]>>> im_bottom = im[2*u:, u:2*u, :]

再生成立方体的六个面在三维空间中的坐标,其中每个面用四个顶点表示,顶点按逆时针方向排列。立方体的棱长为2,也就是xyzz坐标都在[-1,1]范围内。

>>> vs_front = np.array([[-1,-1,1], [-1,-1,-1], [-1,1,-1], [-1,1,1]])>>> vs_left = np.array([[1,-1,1], [1,-1,-1], [-1,-1,-1], [-1,-1,1]])>>> vs_right = np.array([[-1,1,1], [-1,1,-1], [1,1,-1], [1,1,1]])>>> vs_top = np.array([[1,-1,1], [-1,-1,1], [-1,1,1], [1,1,1]])>>> vs_bottom = np.array([[-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1]])>>> vs_back = np.array([[1,-1,1], [1,-1,-1], [1,1,-1], [1,1,1]])

有了六个面的材质和顶点,就可以使用surface函数绘制天空盒了。

>>> plt.surface(vs_front, texture=im_front, alpha=False)>>> plt.surface(vs_left, texture=im_left, alpha=False)>>> plt.surface(vs_right, texture=im_right, alpha=False)>>> plt.surface(vs_top, texture=im_top, alpha=False)>>> plt.surface(vs_bottom, texture=im_bottom, alpha=False)>>> plt.surface(vs_back, texture=im_back, alpha=False)>>> plt.show()

咦?不对啊,为什么我在天空盒外而不是天空盒内呢?

使用python怎么模拟在天空中放风筝

原来,WxGL默认观察者距离坐标原点5个单位的距离,而天空盒在[-1,1]范围内,自然就处于天空盒外了。莫着急,只要设置一下画布函数plt.figure()的参数,就OK了。参数dist用于设置观察者距离观察目标的距离,配合方位角参数azimuth和仰角参数elevation,可以确定观察者位置;参数view用于设置视景体,view数组的6个元素分别表示视景体的左、右、上、下面,以及前后面距离观察者的距离。

>>> plt.figure(dist=0.8, view=[-1, 1, -1, 1, 0.8, 7], elevation=0, azimuth=0)>>> plt.surface(vs_front, texture=im_front, alpha=False)>>> plt.surface(vs_left, texture=im_left, alpha=False)>>> plt.surface(vs_right, texture=im_right, alpha=False)>>> plt.surface(vs_top, texture=im_top, alpha=False)>>> plt.surface(vs_bottom, texture=im_bottom, alpha=False)>>> plt.surface(vs_back, texture=im_back, alpha=False)>>> plt.show()

天空盒最终的效果如下图所示。尝试拖动鼠标、滑动滚轮,你会发现天空盒的缺陷。不过,这不会影响我们放飞风筝。

使用python怎么模拟在天空中放风筝

为了方便后续操作,我们将绘制天空盒的代码封装成一个函数。

>>> def draw_sky_box():        plt.surface(vs_front, texture=im_front, alpha=False)        plt.surface(vs_left, texture=im_left, alpha=False)        plt.surface(vs_right, texture=im_right, alpha=False)        plt.surface(vs_top, texture=im_top, alpha=False)        plt.surface(vs_bottom, texture=im_bottom, alpha=False)        plt.surface(vs_back, texture=im_back, alpha=False)>>>

3.2 第一只风筝

现在观察者位于(0.8,0,0)的位置,假定风筝中心位于v1点(-0.5,-0.3,0.2)的位置(观察者左前上方)。我们需要根据风筝素材的尺寸,确定风筝在空间中的坐标。

>>> im_kite = np.array(Image.open(r'D:\temp\kite\res\butterfly.png')) # 打开风筝图片>>> max_s = max(im_kite.shape) # 风筝的最长边>>> dx, dy = 0.1*im_kite.shape[0]/max_s, 0.1*im_kite.shape[1]/max_s # 计算风筝在空间中的实际尺寸>>> v1 = (-0.5,-0.3,0.2) # 风筝中心位置>>> vs_kite = np.array([[dx,-dy,0.03], [-dx,-dy,0], [-dx,dy,0], [dx,dy,0.03]]) # 风筝四角的坐标,前端略高(后仰0.03)>>> vs_kite[:,0] += v1[0] # 从原点移到v1点>>> vs_kite[:,1] += v1[1] # 从原点移到v1点>>> vs_kite[:,2] += v1[2] # 从原点移到v1点>>> plt.figure(dist=0.8, view=[-1, 1, -1, 1, 0.8, 7], elevation=0, azimuth=0) # 设置画布>>> draw_sky_box() # 绘制天空盒>>> plt.surface(vs_kite, texture=im_kite, alpha=True) # 绘制风筝(png格式需要使用透明通道)>>> plt.show()

至此,终于在草原上放飞了第一只风筝。

使用python怎么模拟在天空中放风筝

3.3 给风筝加上线

风筝线近似于一条悬链线,我们可以用三次曲线模拟。如果放风筝的人在v0点,风筝中心位于v1点,风筝线就可以用k个点来描述。先来定义一个根据v0点和v1点计算风筝线的函数。

>>> def get_line(v0, v1, k=300):        m = np.power(np.linspace(0,k,k), 3)/(k*k*k)        dx, dy = v1[0]-v0[0], v1[1]-v0[1]        x = v1[0] - m*dx        y = v1[1] - m*dy        z = np.linspace(v1[2], v0[2], k)        return x, y, z>>>

重复一遍绘制天空盒和风筝的代码,稍加修改,即可加上风筝线。

>>> v0 = (0.5,0.2,-1) # 放风筝的人在v0点>>> v1 = (-0.5,-0.3,0.2) # 风筝中心位于v1点>>> xs, ys, zs = get_line(v0, v1) # 计算风筝悬链线>>> plt.figure(dist=0.8, view=[-1, 1, -1, 1, 0.8, 7], elevation=0, azimuth=0) # 设置画布>>> draw_sky_box() # 绘制天空盒>>> plt.surface(vs_kite, texture=im_kite, alpha=True) # 绘制风筝>>> plt.plot(xs, ys, zs, color='#C0C0C0', width=0.3) # 绘制风筝悬链线>>> plt.show()

plt.plot()函数用于绘制点或线,参数width用于设置线宽。如果觉得风筝线不够明显,可以适当增加线宽。

使用python怎么模拟在天空中放风筝

3.4 让风筝动起来

想象一下风筝在天空中的飘动姿态,其运动轨迹有两个特点:

水平方向延弧线摆动,幅度约30°左右

摆动到左侧则左侧稍低,摆动到右侧则右侧稍低

据此,不难模拟出风筝的摆动轨迹,计算出运动轨迹线上每一处风筝的坐标,同时计算出对应的风筝悬链线。启动一个定时器,顺序显示轨迹线上每一处风筝及其悬链线,形成动画。

WxGL的plt.surface()函数和plt.plot()函数,支持通过参数slide=True将对应的模型放入一个动画序列,执行plt.show()的时候,会自动播放这个模型序列,时间间隔由plt.figure()函数的interval参数决定,默认值100毫秒。如果多个模型需要同时显示,只需要用name参数为多个模型指定相同的名字即可。

好,我们来定义一个绘制飘动风筝的函数。

>>> def draw_kite(fn, v0, v1, dh=0.03, ex=(-20,20), fs=160):        im_kite = np.array(Image.open(fn)) # 打开风筝图片        max_s = max(im_kite.shape) # 风筝的最长边        dx, dy = 0.1*im_kite.shape[0]/max_s, 0.1*im_kite.shape[1]/max_s # 计算风筝在空间中的实际尺寸            delta = np.hstack((np.linspace(-0.03, 0.03, fs), np.linspace(0.03, -0.03, fs))) # 风筝左右摆动过程中的高度波动        theta = np.hstack((np.linspace(ex[0], ex[1], fs), np.linspace(ex[1], ex[0], fs))) # 风筝左右摆动的角度        vs_kite = np.array([[dx,-dy,dh], [-dx,-dy,0], [-dx,dy,0], [dx,dy,dh]]) # 风筝四角的坐标,前端略高(后仰)        vs_kite[:,0] += v1[0]        vs_kite[:,1] += v1[1]        vs_kite[:,2] += v1[2]            offset = np.random.randint(0, 2*fs)        for i in range(2*fs):            k = (i+offset)%(2*fs)            rotator = Rotation.from_euler('xyz', [0, 0, theta[k]], degrees=True)            vs = rotator.apply(vs_kite)            vs[:2, 2] -= delta[k]            vs[2:, 2] += delta[k]            plt.surface(vs, texture=im_kite, alpha=True, slide=True, name='id_%d'%i)            xs, ys, zs = get_line(v0, ((vs[0][0]+vs[2][0])/2,(vs[0][1]+vs[2][1])/2,(vs[0][2]+vs[2][2])/2))            plt.plot(xs, ys, zs, color='#C0C0C0', width=0.3, slide=True, name='id_%d'%i)    >>>

调用一下试试看。

>>> plt.figure(dist=0.8, view=[-1, 1, -1, 1, 0.8, 7], elevation=0, azimuth=0, interval=50) # 设置画布,动画间隔50毫秒>>> draw_sky_box() # 绘制天空盒>>> draw_kite(r'D:\temp\kite\res\butterfly.png', (0.5,0.2,-1), (-0.5,-0.3,0.2)) # 绘制风筝>>> plt.show()

和我们设想的一样,风筝在[-20°,20°]的范围内左右摆动,悬链线也跟着一起飘动。

使用python怎么模拟在天空中放风筝

3.5 放飞更多的风筝

现在,我们有三张风筝的图片,把它们都放飞到天空盒中吧。至于风筝的位置、放飞者的位置,你可以根据自己的想象,随意定义。

>>> plt.figure(dist=0.8, view=[-1, 1, -1, 1, 0.8, 7], elevation=0, azimuth=0, interval=50)>>> draw_sky_box()>>> draw_kite(r'D:\temp\kite\res\butterfly.png', (0.5,0.2,-1), (-0.5,-0.3,0.2))>>> plt.show()>>> plt.figure(dist=0.8, view=[-1, 1, -1, 1, 0.8, 7], elevation=0, azimuth=0, interval=50)>>> draw_sky_box()>>> draw_kite(r'D:\temp\kite\res\butterfly.png', (0.5,0.2,-1), (-0.5,-0.3,0.2))>>> draw_kite(r'D:\temp\kite\res\fish.png', (0.3,0,-1), (-0.2,-0.1,0.05), ex=(-40,40))>>> draw_kite(r'D:\temp\kite\res\eagle.png', (0.2,0.05,-1), (-0.6,0.5,0.35))>>> plt.show()

至此,大功告成。

使用python怎么模拟在天空中放风筝

4 完整源码

# -*- coding: utf-8 -*-import numpy as npfrom PIL import Imageimport wxgl.wxplot as plt # 交互式3D绘图库from scipy.spatial.transform import Rotation # 空间旋转计算def draw_sky_box(fn):    """绘制天空盒    fn      - 图片文件名(宽高比4:3)    """    im = np.array(Image.open(fn)) # 打开资源图片    u = im.shape[0]//3 # 天空盒(正六面体的棱长)    # 裁切出天空盒6个面:上下前后左右    im_top = im[:u, u:2*u, :]    im_left = im[u:2*u, :u, :]    im_front = im[u:2*u, u:2*u, :]    im_right = im[u:2*u, 2*u:3*u, :]    im_back = im[u:2*u, 3*u:, :]    im_bottom = im[2*u:, u:2*u, :]    # 定义天空盒六个面的顶点坐标,4个顶点按逆时针方向排列    vs_front = np.array([[-1,-1,1], [-1,-1,-1], [-1,1,-1], [-1,1,1]])    vs_left = np.array([[1,-1,1], [1,-1,-1], [-1,-1,-1], [-1,-1,1]])    vs_right = np.array([[-1,1,1], [-1,1,-1], [1,1,-1], [1,1,1]])    vs_top = np.array([[1,-1,1], [-1,-1,1], [-1,1,1], [1,1,1]])    vs_bottom = np.array([[-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1]])    vs_back = np.array([[1,-1,1], [1,-1,-1], [1,1,-1], [1,1,1]])    # 绘制天空盒的六个面    plt.surface(vs_front, texture=im_front, alpha=False)    plt.surface(vs_left, texture=im_left, alpha=False)    plt.surface(vs_right, texture=im_right, alpha=False)    plt.surface(vs_top, texture=im_top, alpha=False)    plt.surface(vs_bottom, texture=im_bottom, alpha=False)    plt.surface(vs_back, texture=im_back, alpha=False)def get_line(v0, v1, k=300):    """风筝线:从风筝底部到放飞者,近似悬链线    v0      - 放飞者坐标    v1      - 风筝底部系线处坐标    k       - 描绘风筝线的点的数量,默认300点    """    m = np.power(np.linspace(0,k,k), 3)/(k*k*k)    dx, dy = v1[0]-v0[0], v1[1]-v0[1]    x = v1[0] - m*dx    y = v1[1] - m*dy    z = np.linspace(v1[2], v0[2], k)    return x, y, zdef draw_kite(fn, v0, v1, dh=0.03, ex=(-20,20), fs=160):    """绘制风筝    fn      - 风筝图片文件名(png格式,带透明通道)    dh      - 风筝后仰高度,默认0.02    ex      - 风筝左右摆动的角度范围    fs      - 风筝随风摆动的帧数    """    im_kite = np.array(Image.open(fn)) # 打开风筝图片    max_s = max(im_kite.shape) # 风筝的最长边    dx, dy = 0.1*im_kite.shape[0]/max_s, 0.1*im_kite.shape[1]/max_s # 计算风筝在空间中的实际尺寸    delta = np.hstack((np.linspace(-0.03, 0.03, fs), np.linspace(0.03, -0.03, fs))) # 风筝左右摆动过程中的高度波动    theta = np.hstack((np.linspace(ex[0], ex[1], fs), np.linspace(ex[1], ex[0], fs))) # 风筝左右摆动的角度    vs_kite = np.array([[dx,-dy,dh], [-dx,-dy,0], [-dx,dy,0], [dx,dy,dh]]) # 风筝四角的坐标,前端略高(后仰)    vs_kite[:,0] += v1[0]    vs_kite[:,1] += v1[1]    vs_kite[:,2] += v1[2]    offset = np.random.randint(0, 2*fs)    for i in range(2*fs):        k = (i+offset)%(2*fs)        rotator = Rotation.from_euler('xyz', [0, 0, theta[k]], degrees=True)        vs = rotator.apply(vs_kite)        vs[:2, 2] -= delta[k]        vs[2:, 2] += delta[k]        plt.surface(vs, texture=im_kite, alpha=True, slide=True, name='id_%d'%i)        xs, ys, zs = get_line(v0, ((vs[0][0]+vs[2][0])/2,(vs[0][1]+vs[2][1])/2,(vs[0][2]+vs[2][2])/2))        plt.plot(xs, ys, zs, color='#C0C0C0', width=0.3, slide=True, name='id_%d'%i)if __name__ == '__main__':    plt.figure(dist=0.8, view=[-1, 1, -1, 1, 0.8, 7], elevation=0, azimuth=0, interval=50)    draw_sky_box('res/sky.jpg')    draw_kite('res/butterfly.png', (0.5,0.2,-1), (-0.5,-0.3,0.2))    draw_kite('res/fish.png', (0.3,0,-1), (-0.2,-0.1,0.05), ex=(-40,40))    draw_kite('res/eagle.png', (0.2,0.05,-1), (-0.6,0.5,0.35))    plt.show()

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注编程网行业资讯频道,感谢您对编程网的支持。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯