From f73aa981189d2369e2e88b7f2780384d8d317b2c Mon Sep 17 00:00:00 2001 From: march3 Date: Thu, 6 Apr 2023 20:23:46 +0800 Subject: [PATCH] =?UTF-8?q?Python=E8=B6=85=E4=BA=BA-=E5=AE=87=E5=AE=99?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sim_scenes/solar_system/speed_of_light.py | 145 ++++++++++++----- simulators/ursina/entities/timer.py | 4 +- simulators/ursina/ursina_event.py | 11 ++ simulators/ursina_simulator.py | 10 +- .../body_view - \345\211\257\346\234\254.py" | 93 ----------- ...mayavi_view - \345\211\257\346\234\254.py" | 152 ------------------ 6 files changed, 122 insertions(+), 293 deletions(-) delete mode 100644 "simulators/views/body_view - \345\211\257\346\234\254.py" delete mode 100644 "simulators/views/mayavi_view - \345\211\257\346\234\254.py" diff --git a/sim_scenes/solar_system/speed_of_light.py b/sim_scenes/solar_system/speed_of_light.py index 0b4ea9d..0a013c9 100644 --- a/sim_scenes/solar_system/speed_of_light.py +++ b/sim_scenes/solar_system/speed_of_light.py @@ -11,28 +11,30 @@ from common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY, SECONDS_PER_YEAR, A from sim_scenes.func import mayavi_run, ursina_run from simulators.ursina.ursina_config import UrsinaConfig from simulators.ursina.ursina_event import UrsinaEvent +from ursina import Text, Panel, color, camera -if __name__ == '__main__': - # 八大行星:木星(♃)、土星(♄)、天王星(♅)、海王星(♆)、地球(⊕)、金星(♀)、火星(♂)、水星(☿) - # 排列顺序 - # 1、体积:(以地球为1)木星 :土星 :天王星 :海王星 :地球 :金星 :火星 :水星 = 1330:745:65:60:1:0.86:0.15:0.056 - # 2、质量:(以地球为1)木星 :土星 :天王星 :海王星 :地球 :金星 :火星 :水星 = 318:95:14.53:17.15:1:0.8:0.11:0.0553 - # 3、离太阳从近到远的顺序:水星、金星、地球、火星、木星、土星、天王星、海王星 - # ===================================================================== - # 以下展示的效果为太阳系真实的距离 - # 由于宇宙空间尺度非常大,如果按照实际的天体大小,则无法看到天体,因此需要对天体的尺寸进行放大 - sun = Sun(name="太阳", size_scale=0.8e2) # 太阳放大 80 倍,距离保持不变 + +def create_bodies(): + """ + 创建太阳系天体(忽略质量,引力无效,初速度全部为0) + 太阳、小行星环、 + 八大行星:木星(♃)、土星(♄)、天王星(♅)、海王星(♆)、地球(⊕)、金星(♀)、火星(♂)、水星(☿) + 冥王星 + 以下展示的效果为太阳系真实的距离 + @return: + """ + sun = Sun(name="太阳", size_scale=0.6e2) # 太阳放大 60 倍,距离保持不变 bodies = [ sun, - Mercury(name="水星", size_scale=4e3), # 水星放大 4000 倍,距离保持不变 + Mercury(name="水星", size_scale=3e3), # 水星放大 3000 倍,距离保持不变 Venus(name="金星", size_scale=4e3), # 金星放大 4000 倍,距离保持不变 Earth(name="地球", size_scale=4e3), # 地球放大 4000 倍,距离保持不变 Mars(name="火星", size_scale=4e3), # 火星放大 4000 倍,距离保持不变 Asteroids(name="小行星群", size_scale=3.2e2, parent=sun), # 小行星群模拟(仅 ursina 模拟器支持) - Jupiter(name="木星", size_scale=0.8e3), # 木星放大 800 倍,距离保持不变 - Saturn(name="土星", size_scale=0.8e3), # 土星放大 800 倍,距离保持不变 - Uranus(name="天王星", size_scale=0.8e3), # 天王星放大 800 倍,距离保持不变 + Jupiter(name="木星", size_scale=0.7e3), # 木星放大 600 倍,距离保持不变 + Saturn(name="土星", size_scale=0.7e3), # 土星放大 600 倍,距离保持不变 + Uranus(name="天王星", size_scale=0.7e3), # 天王星放大 600 倍,距离保持不变 Neptune(name="海王星", size_scale=1e3), # 海王星放大 1000 倍,距离保持不变 Pluto(name="冥王星", size_scale=10e3), # 冥王星放大 10000 倍,距离保持不变(从太阳系的行星中排除) ] @@ -41,41 +43,98 @@ if __name__ == '__main__': for idx, body in enumerate(bodies): body.set_ignore_mass(True) # 忽略质量(引力无效) body.init_velocity = [0, 0, 0] # 初速度为0 + return bodies + + +def create_light(): + """ + 用天体模拟一个光子 + @return: + """ + return Body(name='光速', mass=0, size_scale=1e4, color=(255, 255, 0), + init_position=[AU / 2, 0, 0], + init_velocity=[0, 0, 299792.458]).set_light_disable(True) # 1光速=299792.458 千米/秒(km/秒) + + +def create_text_panel(width=0.35, height=.5): + # 创建一个 Panel 组件 + panel = Panel( + parent=None, + model='quad', + # texture='white_cube', + color=color.gray, + origin=(-.48, .48), + scale=(width, height), + position=(-.88, 0.3, 0), + alpha=0.2 + ) + + # 创建一个 Text 组件用于显示消息 + text = Text( + parent=panel, + text='', + origin=(-.5, .5), + scale=(height * 5, width * 5), + font=UrsinaConfig.CN_FONT, + # background=True, + # background_color=color.clear + ) + return text + + +# 已到达天体列表 +arrived_bodies = [] +text_panel = None +arrived_info = "" + + +def on_reset(): + global arrived_info + arrived_bodies.clear() + arrived_info = "" + if text_panel is not None: + text_panel.text = "" + - # 用天体模拟一个光子 - light_body = Body(name='光', mass=0, size_scale=1e4, color=(255, 255, 0), - init_position=[AU / 2, 0, 0], - init_velocity=[0, 0, 299792.458]).set_light_disable(True) # 1 光速=299792.458 千米/秒(km/秒) +def on_ready(): + global text_panel + text_panel = create_text_panel() - bodies.append(light_body) - arrived_bodies = [] +def on_timer_changed(time_text, time_data): + global arrived_info + years, days, hours, minutes, seconds = time_data + for body in bodies: + if body is light_body or isinstance(body, Sun) \ + or body in arrived_bodies or isinstance(body, Asteroids): + # 对于光速天体、太阳、小行星群、“已到达天体列表”中的天体无需计算 + continue + # 计算判断,如果光速天体距离到达了某个天体,就记录到“已到达天体列表”中 + if light_body.position[2] >= body.position[2]: + arrived_bodies.append(body) + if text_panel is not None: + arrived_info += f"[{time_text}]\t到达\t[{body.name}]\n\n" + text_panel.text = arrived_info + print(f"[{time_text}] 到达 [{body.name}]") - def on_reset(): - arrived_bodies.clear() - def on_timer_changed(time_text, time_data): - years, days, hours, minutes, seconds = time_data - # UrsinaEvent.on_timer_changed(self.text, (years, days, hours, minutes, seconds)) - # 光到达地球8.3分钟, - # 光到达冥王星平均用时要20000秒,333.3分钟 也就是约5.56小时 +# 订阅计时器事件(记录已到达天体列表) +UrsinaEvent.on_timer_changed_subscription(on_timer_changed) +# 订阅重新开始事件 +UrsinaEvent.on_reset_subscription(on_reset) - for body in bodies: - if body is light_body or isinstance(body, Sun) \ - or body in arrived_bodies or isinstance(body, Asteroids): - continue - if light_body.position[2] >= body.position[2]: - arrived_bodies.append(body) - print(f"[{time_text}] 已经到达 [{body.name}]") - # print(light_body.planet) +UrsinaEvent.on_ready_subscription(on_ready) +# 创建太阳系天体(忽略质量,引力无效,初速度全部为0) +bodies = create_bodies() +# 创建一个以光速前进的天体(模拟一个光子,质量为0才能达到光速) +light_body = create_light() - UrsinaEvent.on_timer_changed_subscription(on_timer_changed) - UrsinaEvent.on_reset_subscription(on_reset) +bodies.append(light_body) - # 使用 ursina 查看的运行效果 - # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 - # position = 左-右+、上+下-、前+后- - ursina_run(bodies, 60, position=(0, 2 * AU, -11 * AU), - show_trail=True, show_timer=True, - bg_music="sounds/interstellar.mp3") +# 使用 ursina 查看的运行效果 +# 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 +# position = 左-右+、上+下-、前+后- +ursina_run(bodies, 60, position=(0, 2 * AU, -11 * AU), + show_trail=True, show_timer=True, + bg_music="sounds/interstellar.mp3") diff --git a/simulators/ursina/entities/timer.py b/simulators/ursina/entities/timer.py index 446690d..f4408db 100644 --- a/simulators/ursina/entities/timer.py +++ b/simulators/ursina/entities/timer.py @@ -39,8 +39,8 @@ class Timer(Text): # self.text = f'{minutes:02d}:{seconds:02d}' time_scale = UrsinaConfig.get_app_time_scale() current_time = datetime.datetime.now() - # 0.653 是对测试太阳系时间的纠正 - self.elapsed_time += (current_time - self.last_time) * evolve_dt * time_scale * 0.653 + # 0.6 是对测试太阳系时间的纠正 + self.elapsed_time += (current_time - self.last_time) * evolve_dt * time_scale * 0.6 # datetime.timedelta(microseconds=1) 0:00:00.000001 # datetime.timedelta(milliseconds=1) 0:00:00.001000 # self.elapsed_time += self.elapsed_time_offset # 按区域取值 diff --git a/simulators/ursina/ursina_event.py b/simulators/ursina/ursina_event.py index 198d195..8e54d49 100644 --- a/simulators/ursina/ursina_event.py +++ b/simulators/ursina/ursina_event.py @@ -24,6 +24,8 @@ class UrsinaEvent: UrsinaEvent.on_pause_funcs = [] # 启动运行的订阅事件 UrsinaEvent.on_start_funcs = [] + # 运行准备的订阅事件 + UrsinaEvent.on_ready_funcs = [] # 搜索天体的订阅事件 UrsinaEvent.on_searching_bodies_funcs = [] @@ -80,6 +82,15 @@ class UrsinaEvent: for f in UrsinaEvent.on_reset_funcs: f() + @staticmethod + def on_ready_subscription(fun): + UrsinaEvent.on_ready_funcs.append(fun) + + @staticmethod + def on_ready(): + for f in UrsinaEvent.on_ready_funcs: + f() + @staticmethod def on_start_subscription(fun): UrsinaEvent.on_start_funcs.append(fun) diff --git a/simulators/ursina_simulator.py b/simulators/ursina_simulator.py index 7f38a27..ed1d579 100644 --- a/simulators/ursina_simulator.py +++ b/simulators/ursina_simulator.py @@ -242,7 +242,8 @@ class UrsinaSimulator(Simulator): camera.fov = 60 window.fps_counter.enabled = False - window.editor_ui.enabled = False + # window.exit_button.enabled = False + # window.editor_ui.enabled = True # # 场景加入雾的效果 # scene.fog_color = color.orange @@ -275,8 +276,6 @@ class UrsinaSimulator(Simulator): if cosmic_bg is not None and os.path.exists(cosmic_bg): self.cosmic_background(cosmic_bg) - - # ui = UrsinaUI() ctl = ControlUI(ControlHandler(), position=(0.6, 0.5)) if show_timer: @@ -309,6 +308,11 @@ class UrsinaSimulator(Simulator): audio = Audio(bg_music, pitch=1, loop=True, autoplay=True) audio.volume = 0.3 + if show_timer: + UrsinaEvent.on_reset() + + UrsinaEvent.on_ready() + self.app.run() diff --git "a/simulators/views/body_view - \345\211\257\346\234\254.py" "b/simulators/views/body_view - \345\211\257\346\234\254.py" deleted file mode 100644 index 3bd3bb7..0000000 --- "a/simulators/views/body_view - \345\211\257\346\234\254.py" +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding:utf-8 -*- -# title :天体视图 -# description :天体视图(天体效果展示用) -# author :Python超人 -# date :2023-02-11 -# link :https://gitcode.net/pythoncr/ -# python_version :3.8 -# ============================================================================== -from abc import ABCMeta, abstractmethod -from bodies import Body -from common.func import get_dominant_colors -import numpy as np -import os - - -class BodyView(metaclass=ABCMeta): - """ - 天体视图(天体效果展示用) - """ - - def __init__(self, body: Body): - self.body = body - self.sphere = None - if self.body.texture is None or self.body.texture == '': - self.color = tuple(np.array(body.color) / 255) - else: - self.texture = self.__find_texture(self.body.texture) # 纹理 - if self.texture is None: - self.color = tuple(np.array(body.color) / 255) - else: - self.color = self.__get_texture_main_color(self.texture) - self.appear() - self.position = [None, None, None] - self.name = None - self.mass = None - self.raduis = None - self.velocity = None - self.angle = 0 - - self.appeared = True - - def __repr__(self): - return '<%s> m=%.3e(kg), r=%.3e(km), p=[%.3e,%.3e,%.3e](km), v=%s(km/s)' % \ - (self.name, self.mass, self.raduis, - self.position[0], self.position[1], self.position[2], self.velocity) - - def __find_texture(self, texture): - """ - 尝试在多个路径下寻找纹理图片 - :param texture: 纹理图片 - :return: 纹理图片的路径 - """ - paths = ['./textures', '../textures'] - for path in paths: - p = path + "/" + texture - if os.path.exists(p): - return p - - return None - - def __get_texture_main_color(self, texture): - """ - 获取纹理图片的主要颜色 - :param texture: - :return: - """ - colors = get_dominant_colors(texture) - first_color = colors[0] - # print(self.name, first_color) - return tuple(np.array(first_color) / 255) - - @abstractmethod - def update(self): - """ - 更新天体信息和数据,比如:更新天体的位置 - :return: - """ - pass - - def disappear(self): - """ - 天体消失的操作,比如:销毁天体视图对象 - :return: - """ - pass - - @abstractmethod - def appear(self): - """ - 天体显示的操作,比如:构建天体视图对象 - :return: - """ - pass diff --git "a/simulators/views/mayavi_view - \345\211\257\346\234\254.py" "b/simulators/views/mayavi_view - \345\211\257\346\234\254.py" deleted file mode 100644 index d757b46..0000000 --- "a/simulators/views/mayavi_view - \345\211\257\346\234\254.py" +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding:utf-8 -*- -# title :Mayavi天体视图 -# description :Mayavi天体视图(天体效果展示用,需要安装 mayavi) -# author :Python超人 -# date :2023-02-11 -# link :https://gitcode.net/pythoncr/ -# python_version :3.8 -# ============================================================================== -from mayavi import mlab -from tvtk.api import tvtk -import os -import matplotlib.pyplot as plt -from common.func import get_dominant_colors - -from simulators.views.body_view import BodyView -import numpy as np - - -class MayaviView(BodyView): - """ - Mayavi天体视图(天体效果展示用) - """ - - def update(self): - """ - 更新天体信息和数据,比如:更新天体的位置 - :return: - """ - if hasattr(self.sphere, "mlab_source"): - # self.sphere.mlab_source.x 的位置是已经和 distance_scale 进行了相乘 - # body.position 是真实位置,所以需要和 distance_scale 相乘 - x_offset = self.body.position[0] * self.body.distance_scale - self.sphere.mlab_source.x - y_offset = self.body.position[1] * self.body.distance_scale - self.sphere.mlab_source.y - z_offset = self.body.position[2] * self.body.distance_scale - self.sphere.mlab_source.z - # self.position 的位置是已经和 distance_scale 进行了相乘 - self.sphere.mlab_source.set(x=self.position[0], y=self.position[1], z=self.position[2]) - # self.angle += 10 - # self.sphere.actor.actor.rotate_z(self.angle) - # self.sphere.actor.actor.rotate_wxyz(self.angle,0,0,0) - - if hasattr(self, "rings"): - if hasattr(self.rings, "mlab_source"): - if hasattr(self, "rings") and self.body.has_rings: - x = self.rings.mlab_source.x - y = self.rings.mlab_source.y - z = self.rings.mlab_source.z - x = x + x_offset[0] - y = y + y_offset[0] - z = z + z_offset[0] - self.rings.mlab_source.set(x=x, y=y, z=z) - - # return x_offset[0], y_offset[0], z_offset[0] - - def build_rings(self): - if not hasattr(self, "rings") or self.rings is None: - - R = 2.2 - r = 0.5 - rings_scale = 0.5e5 - resolution = 50 - theta = np.linspace(0, 2 * np.pi, resolution) - phi = np.linspace(0, 2 * np.pi, resolution) - torus = np.zeros((3, resolution, resolution)) - - # # body.position 是真实位置,所以需要和 distance_scale 相乘 - for i in range(0, resolution): - for j in range(0, resolution): # size_scale=8.0e2 - torus[0][i][j] = (R + r * np.cos(phi[j])) * np.cos(theta[i]) * \ - self.body.size_scale * rings_scale + \ - self.body.position[0] # * self.body.distance_scale - - torus[1][i][j] = (R + r * np.cos(phi[j])) * np.sin(theta[i]) * \ - self.body.size_scale * rings_scale + \ - self.body.position[1] # * self.body.distance_scale - # 带环的厚度 - thicknesses_scale = self.body.raduis * 20 - torus[2][i][j] = thicknesses_scale * np.sin(phi[j]) + \ - self.body.position[2] # * self.body.distance_scale - rings_color = (173 / 255, 121 / 255, 92 / 255) - if hasattr(self.body, "rings_color"): - rings_color = tuple(np.array(self.body.rings_color) / 255) - self.rings = mlab.mesh(torus[0], torus[1], torus[2], color=rings_color, - representation='surface') - return self.rings - - def appear(self): - """ - 天体显示的操作,比如:构建天体视图对象 - :return: - """ - if not hasattr(self, "sphere") or self.sphere is None: - scale_factor = self.body.size_scale * self.body.diameter - sphere = mlab.points3d(self.body.position[0], self.body.position[1], self.body.position[2], - scale_factor=scale_factor, - color=self.color, - resolution=50, - opacity=1, - name=self.body.name) - # # 调整镜面反射参数 - sphere.actor.property.specular = 0.5 # 0.1 - sphere.actor.property.specular_power = 128 - # 设置背面剔除,以更好的显示透明效果 - sphere.actor.property.backface_culling = True - # sphere.actor.property.lighting_ = 10 - sphere.actor.property.lighting = False - sphere.scene.disable_render = False - # sphere_earth.scene.disable_render = False - - sphere.scene.anti_aliasing_frames = 10 - # sphere_earth.scene.anti_aliasing_frames = 0 - self.sphere = sphere - - if hasattr(self, "texture"): - if self.texture is not None and self.texture != '': - self.__set_texture(self.texture) - - if self.body.has_rings: - self.build_rings() - # return self.sphere, self.rings - - # return self.sphere, - - def disappear(self): - if hasattr(self, "sphere"): - self.sphere.visible = False - - if hasattr(self, "rings"): - self.rings.visible = False - - def __set_texture(self, image_file): - """ - 设置纹理图片到天体 - :param image_file: - :return: - """ - outfile = image_file.replace('.jpg', '_flipped.jpg') - if os.path.exists(outfile): - image_file = outfile - else: - if not os.path.exists(image_file): - return - img = plt.imread(image_file) - img = img[::-1, ...] - plt.imsave(outfile, img) - image_file = outfile - - img = tvtk.JPEGReader(file_name=image_file) - texture = tvtk.Texture(input_connection=img.output_port, interpolate=0, repeat=0) - self.sphere.actor.actor.texture = texture - self.sphere.actor.tcoord_generator_mode = 'sphere' - cylinder_mapper = self.sphere.actor.tcoord_generator - cylinder_mapper.prevent_seam = 0 -- GitLab