3D 移动

建立日期: 2020/05/02

修订日期: 2020/05/08 (Revised for Tool.py PEP8)

相关软件信息:

Win 10 Python 3.7.6 PySimpleGUI 4.18.2 PIL/Pillow 7.1.1

Tool

说明: 本文请随意引用或更改,只须标示出处及作者,作者不保证内容绝对正确无误,如造成任何后果,请自行负责.

前言

一个3D 移动的程序, 169 行.

内容说明

  1. 固定的远景
  2. 马路
  3. 分路线
  4. 路边
  5. 路樹
  6. 其他绿地
  7. 向前移动
  8. 3D

输出画面

3D 移动

代码及说明

  1. 库的导入
import PySimpleGUI as sg
import math
from pathlib import Path
from Tool import read_URL, mapping
from PIL import Image
from io import BytesIO
  1. 其本参数设定: 物件长寛, 颜色及数量, 摄像头位置, 屏幕位置,
class GUI():

    def __init__(self):
        self.boxes = 51
        self.box_height = 50
        self.x0, self.y0, self.z0 = 0, 150, -50
        self.z1 = 0
        self.gap_width = 860
        self.road_width = 800
        self.line_width = 20
        self.offset = 0.5
        self.road_color = 'grey'
        self.gap_color = 'black'
        self.line_color = 'white'
        self.figures = []
        self.count = 0
        self.Create_Window()
  1. 建立画布物件

    def Create_Canvas(self):
        self.width = 1080
        self.height = 400
        return sg.Graph((self.width, self.height), (-self.width//2, 0),
            (self.width//2, self.height), key='Graph')
  1. 建立 GUI 视窗, 从网络上下载景及树木的图片, 并画上背景
    def Create_Window(self):
        self.window = sg.Window('3D Road', layout=[[self.Create_Canvas()]],
            return_keyboard_events=True, finalize=True)
        self.draw = self.window.FindElement('Graph')
        self.Load_Background()
        self.Load_Tree()
        self.Draw_Background()
  1. 下载图片, 并以要求的尺寸存盘
    def Download_Picture(self, url, file, size):
        if not Path(file).is_file():
            sg.popup(f'First time to load {file} from web ...', no_titlebar=True,
                auto_close=True, auto_close_duration=2)
            response, data = read_URL(url, byte=True)
            if data:
                with open(file, 'wb') as f:
                    f.write(data)
                im = read_file(file)
                im = im.resize(size)
                im.save(file)
                sg.popup(f'{file} loaded ...', no_titlebar=True,
                    auto_close=True, auto_close_duration=1)
            else:
                sg.popup(f'{file} load failed...', no_titlebar=True,
                    auto_close=True, auto_close_duration=2)
                self.window.close()
                quit()
  1. 转换图片文件成适合显示的数据
    def Load_Data(self, file):
        im = Image.open(file)
        with BytesIO() as output:
            im.save(output, format='PNG')
            data = output.getvalue()
        return (data, im.width, im.height)
  1. 载入背景图片
    def Load_Background(self):
        url = 'https://ae01.alicdn.com/kf/HTB18XRYXBv0gK0jSZKb762K2FXaF.png'
        file = 'background.png'
        size = (self.width, 300)
        self.Download_Picture(url, file, size)
        self.background, self.background_width, self.background_height = (
            self.Load_Data(file))
  1. 载入树木图片
    def Load_Tree(self):
        url = 'https://www.vippng.com/png/full/458-4585828_fall-tree-png.png'
        file = 'tree.png'
        size = (250, 250)
        self.Download_Picture(url, file, size)
        self.tree = Image.open(file)
        self.tree_width, self.tree_height = self.tree.size
  1. 转换 3D 座标成 2D 座标
    def Point_To_2D(self, point):
        x, y, z = point
        s1 = (z-self.z1)/(z-self.z0)
        X = x + (self.x0 - x)*s1 - self.x0
        Y = y + (self.y0 - y)*s1
        return [X, Y]
  1. 根据左下角的标及其变化, 生成框的四个角座标
    def Point_To_Box(self, x, y, z, dx, dy, dz, dx2):
        return [[x, y, z], [x+dx, y, z],
                [x+dx+dx2, y+dy, z+dz], [x+dx2, y+dy, z+dz]]
  1. 显示背景图片
    def Draw_Background(self):
        self.background_figure = self.draw.DrawImage(data=self.background,
            location=(-self.background_width//2, self.height))
  1. 显示一个填色的框
    def Draw_Box(self, x, y, z, dx, dy, dz, dx2, color):
        points = self.Point_To_Box(x, y, z, dx, dy, dz, dx2)
        points = mapping(self.Point_To_2D, points)
        self.figures.append(self.draw.DrawPolygon(points, fill_color=color))
  1. 显示绿地
    def Draw_Grass(self, x, y, z, dx, dy, dz, dx2, color):
        points = self.Point_To_Box(x, y, z, dx, dy, dz, dx2)
        points = mapping(self.Point_To_2D, points)
        points[0][0] = points[3][0] = -self.width//2
        points[1][0] = points[2][0] = self.width//2
        self.figures.append(self.draw.DrawPolygon(points, fill_color=color))
  1. 显示左右两边的树木, 并按比率缩小
    def Draw_Tree(self, x, y, z, dx, dy, dz, scale=1):
        w = max(1, int(self.tree_width*scale))
        h = max(1, int(self.tree_height*scale))
        im = self.tree.resize((w, h))
        with BytesIO() as output:
            im.save(output, format='PNG')
            data = output.getvalue()
        X, Y = self.Point_To_2D([x, y, z])
        figure = self.draw.DrawImage(data=data,
            location = (X-self.tree_width*scale/2, Y+self.tree_height*scale))
        self.figures.append(figure)
        X, Y = self.Point_To_2D([x+dx, y+dy, z+dy])
        figure = self.draw.DrawImage(data=data,
            location = (X-self.tree_width*scale/2, Y+self.tree_height*scale))
        self.figures.append(figure)
  1. 删除记录中所有已显示的物件
    def Delete_Figures(self):
        for figure in self.figures:
            self.draw.DeleteFigure(figure)
        self.figures = []
  1. 屏幕更新
  • 先删除已画的物件
  • 画绿地
  • 由远到近把路边, 马路, 中线以及树画出
  • 马路的中心位置由sin函数来调整
  • 马路高低也由sin函数来调整
  • 树的放大比率也随着距离变化
    def Update_All(self):
        self.Delete_Figures()
        self.Draw_Grass(-self.width//2, 0, self.offset,
            self.width, 0, self.boxes*self.box_height, 0, 'green')
        for box in range(self.boxes-1, -1, -1):
            center = -500*math.sin(2*(box+self.count)/self.boxes*math.pi)
            dx = -500*math.sin(2*(box+self.count+1)/self.boxes*math.pi)-center
            dy = center/2-250
            z = box*self.box_height
            if (box+self.count)%3 == 0:
                self.Draw_Box(center-self.gap_width//2, 0, z, self.gap_width,
                    0, self.box_height, dx, self.gap_color)
            self.Draw_Box(center-self.road_width//2, 0, z, self.road_width,
                    0, self.box_height, dx, self.road_color)
            if (box+self.count)%3 == 0:
                self.Draw_Box(center-self.line_width//2, 0, z, self.line_width,
                    0, self.box_height, dx, self.line_color)
            if (box+self.count)%10 == 0:
                scale = 1- (z-self.z1)/(z-self.z0)
                self.Draw_Tree(center-self.gap_width//2-50, 0, z,
                    self.gap_width+100, 0, 0, scale)
        self.count += 1
        if self.count == self.boxes:
            self.count = 0
        self.x0 = center
  1. 简单的GUI事件处理回路
G = GUI()
while True:

    G.Update_All()
    event, values = G.window.Read(timeout=100)

    if event == None:
        break

    elif event in ['Left:37', 'Right:39', 'Up:38', 'Down:40']:
        pass

G.window.close()
本作品采用《CC 协议》,转载必须注明作者和本文链接
Jason Yang
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!