diff --git a/bodies/body.py b/bodies/body.py index 981b75db7a41f0156b42906733c924c7c8ba52b8..c0b2d385da56553b09f055d21cb8c4cb363af796 100644 --- a/bodies/body.py +++ b/bodies/body.py @@ -45,8 +45,6 @@ class Body(metaclass=ABCMeta): self.__his_vel = [] self.__his_acc = [] self.__his_reserved_num = 200 - # 是否忽略质量(如果为True,则不计算引力) - self.ignore_mass = ignore_mass if name is None: name = getattr(self.__class__, '__name__') @@ -54,6 +52,12 @@ class Body(metaclass=ABCMeta): self.name = name self.__mass = mass + if self.__mass <= 0: # 质量小于等于0就忽略 + self.ignore_mass = True + else: + # 是否忽略质量(如果为True,则不计算引力) + self.ignore_mass = ignore_mass + self.__init_position = None self.__init_velocity = None @@ -92,7 +96,19 @@ class Body(metaclass=ABCMeta): self.__has_rings = False - def set_light_disable(self, value): + def set_ignore_mass(self, value=True): + """ + 设置忽略质量,True为引力失效 + @param value: + @return: + """ + if self.__mass <= 0: # 质量小于等于0就忽略 + self.ignore_mass = True + else: + self.ignore_mass = value + return self + + def set_light_disable(self, value=True): """ 设置灯光为无效 @param value: @@ -311,6 +327,9 @@ class Body(metaclass=ABCMeta): """ 天体的体积(单位:km³) """ + if self.mass <= 0: + # 质量为0或者负数,就给一个虚拟体积数 + return 1e10 # v = m/ρ # 体积(m³) = 质量(kg) / 密度(kg/m³) # 体积(km³) = 体积(m³) / 1e9 diff --git a/sim_scenes/earth/free_fall_of_ball.py b/sim_scenes/earth/free_fall_of_ball.py index 89de9f9f10608078d33a52017c474acca5086379..385a90f77ab92eb2020f2b31d7d68bec64586d26 100644 --- a/sim_scenes/earth/free_fall_of_ball.py +++ b/sim_scenes/earth/free_fall_of_ball.py @@ -24,4 +24,7 @@ if __name__ == '__main__': # 使用 ursina 查看的运行效果 # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 # position = 左-右+、上+下-、前+后- - ursina_run(bodies, 60, position=(0, e.raduis + 500, -4500), show_trail=True, view_closely=0.001) + ursina_run(bodies, 60, position=(0, e.raduis + 500, -4500), + show_trail=True, + show_timer=True, + view_closely=0.001) diff --git a/sim_scenes/func.py b/sim_scenes/func.py index 451e4a944e7d9f6c8057e296f98d1826517c2058..8456dd739bb04df56f40a8a0a66556f247c19995 100644 --- a/sim_scenes/func.py +++ b/sim_scenes/func.py @@ -64,6 +64,7 @@ def ursina_run(bodies, show_grid=True, show_trail=False, show_name=False, + show_timer=False, save_as_json=None, view_closely=False): """ @@ -76,12 +77,14 @@ def ursina_run(bodies, @param show_grid: 是否显示空间网格 @param show_trail: 是否显示拖尾 @param show_name: 是否显示天体名称 + @param show_timer: 是否显示计时器 @param save_as_json: 将所有天体的信息保存为 json 文件 @param view_closely: 是否近距离查看天体 @return: """ - from simulators.ursina_simulator import UrsinaSimulator, UrsinaPlayer + from simulators.ursina_simulator import UrsinaSimulator + from simulators.ursina.entities.ursina_player import UrsinaPlayer body_sys = System(bodies) if show_name: @@ -100,7 +103,6 @@ def ursina_run(bodies, view_azimuth = 0 # 暂时未用 player = UrsinaPlayer(position, view_azimuth, simulator.ursina_views) - def callback_update(): UrsinaEvent.on_application_run() for ursina_view in simulator.ursina_views: @@ -116,6 +118,7 @@ def ursina_run(bodies, simulator.run(dt, cosmic_bg=cosmic_bg, show_grid=show_grid, + show_timer=show_timer, bg_music=bg_music, view_closely=view_closely) diff --git a/sim_scenes/solar_system/speed_of_light.py b/sim_scenes/solar_system/speed_of_light.py new file mode 100644 index 0000000000000000000000000000000000000000..6acb96a8488908a74bb432d46568bc3b23cdf0b6 --- /dev/null +++ b/sim_scenes/solar_system/speed_of_light.py @@ -0,0 +1,57 @@ +# -*- coding:utf-8 -*- +# title :太阳系场景模拟1 +# description :太阳系场景模拟(展示的效果为太阳系真实的距离) +# author :Python超人 +# date :2023-02-11 +# link :https://gitcode.net/pythoncr/ +# python_version :3.8 +# ============================================================================== +from bodies import Sun, Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, Moon, Asteroids, Body +from common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY, SECONDS_PER_YEAR, AU +from sim_scenes.func import mayavi_run, ursina_run + +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 倍,距离保持不变 + bodies = [ + sun, + Mercury(name="水星", size_scale=4e3), # 水星放大 4000 倍,距离保持不变 + 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 倍,距离保持不变 + Neptune(name="海王星", size_scale=1e3), # 海王星放大 1000 倍,距离保持不变 + Pluto(name="冥王星", size_scale=10e3), # 冥王星放大 10000 倍,距离保持不变(从太阳系的行星中排除) + ] + + # 遍历所有天体, + for idx, body in enumerate(bodies): + body.set_ignore_mass(True) # 忽略质量(引力无效) + body.init_velocity = [0, 0, 0] # 初速度为0 + + # 用天体模拟一个光子 + 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/秒) + + 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") + + # 光到达地球8.3分钟, + # 光到达冥王星平均用时要20000秒,333.3分钟 也就是约5.56小时 diff --git a/simulators/ursina/entities/timer.py b/simulators/ursina/entities/timer.py new file mode 100644 index 0000000000000000000000000000000000000000..573d7b03181b88ff0273e5d2ab7769d081f84d49 --- /dev/null +++ b/simulators/ursina/entities/timer.py @@ -0,0 +1,64 @@ +from ursina import Text, Ursina, application +import datetime + +from simulators.ursina.ursina_config import UrsinaConfig +from simulators.ursina.ursina_event import UrsinaEvent + + +class Timer(Text): + + def __init__(self): + # 创建一个文本对象来显示计时器的时间 + super().__init__(text='00:00', position=(-0.85, 0.49), font=UrsinaConfig.CN_FONT) + # 用来计时的变量 + # self.start_time = time.time() + self.reset() + UrsinaEvent.on_timer_changed_subscription(self.update) + UrsinaEvent.on_reset_subscription(self.reset) + UrsinaEvent.on_pause_subscription(self.pause) + UrsinaEvent.on_start_subscription(self.start) + self.elapsed_time_offset = datetime.timedelta(microseconds=1) + + def pause(self): + pass + + def start(self): + self.last_time = datetime.datetime.now() + + def reset(self): + self.last_time = datetime.datetime.now() + self.elapsed_time = datetime.timedelta(0) + + def update(self, evolve_dt=1): + # # 计算当前的时间 + # elapsed_time = time.time() - self.start_time + # + # # 将时间转换成“分钟:秒”的形式 + # minutes = int(elapsed_time // 60) + # seconds = int(elapsed_time % 60) + # 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 + # datetime.timedelta(microseconds=1) 0:00:00.000001 + # datetime.timedelta(milliseconds=1) 0:00:00.001000 + self.elapsed_time += self.elapsed_time_offset # 按区域取值 + self.last_time = current_time + hours, remainder = divmod(self.elapsed_time.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + days = self.elapsed_time.days + self.text = f'{days}天, {hours:02d}:{minutes:02d}:{seconds:02d}' + + +if __name__ == '__main__': + app = Ursina() + + t = Timer() + + + def update(): + t.update() + + + app.run() diff --git a/simulators/ursina/ui/control_handler.py b/simulators/ursina/ui/control_handler.py index 3c05b9443b2566ccd07e121e6079d5dc2db82c00..737583837f8be146d7b53ca2744df851e303577c 100644 --- a/simulators/ursina/ui/control_handler.py +++ b/simulators/ursina/ui/control_handler.py @@ -182,6 +182,7 @@ class ControlHandler(EventHandler): if self.ui.on_off_switch.value == self.ui.pause_button_text: self.ui.on_off_switch.selected_color = color.green application.paused = True + UrsinaEvent.on_pause() for c in self.ui.children: if not c.ignore_paused: # c.enabled = True @@ -189,6 +190,7 @@ class ControlHandler(EventHandler): else: self.ui.on_off_switch.selected_color = color.red application.paused = False + UrsinaEvent.on_start() for c in self.ui.children: if not c.ignore_paused: # c.enabled = True diff --git a/simulators/ursina/ui/control_ui.py b/simulators/ursina/ui/control_ui.py index 9967e281aed3ee1e1a71d21b190afb6748263e33..ab5d99a506c70ff40985bd0076a852d1ffb9907d 100644 --- a/simulators/ursina/ui/control_ui.py +++ b/simulators/ursina/ui/control_ui.py @@ -31,8 +31,8 @@ class ControlUI(UiPanel): self.trail_button_text = "○--" self.slider_body_spin_factor = UiSlider(text='自转速度', min=0.01, max=5, default=1) - self.slider_body_size_factor = UiSlider(text='天体缩放', min=0.1, max=100, step=0.1, default=1) - self.slider_run_speed_factor = UiSlider(text="运行速度", min=0.01, max=80, default=1) + self.slider_body_size_factor = UiSlider(text='天体缩放', min=0.1, max=10, step=0.1, default=1) + self.slider_run_speed_factor = UiSlider(text="运行速度", min=0.01, max=20, default=1) self.slider_control_speed_factor = UiSlider(text="控制速度", min=0.01, max=10, step=0.1, default=application.time_scale) self.slider_trail_length = UiSlider(text="拖尾长度", min=30, max=500, step=10, default=UrsinaConfig.trail_length) diff --git a/simulators/ursina/ui_component.py b/simulators/ursina/ui_component.py index 2ab63b81595ec8ad8608f07d11934c6710c65555..4d7dd36ab6b58d41218cd0d15b634d0a3f9f3890 100644 --- a/simulators/ursina/ui_component.py +++ b/simulators/ursina/ui_component.py @@ -19,6 +19,7 @@ class UiSlider(Slider): """ """ + def __init__(self, text, min=0.01, max=3, step=.01, default=1): # Text.default_font = 'msyhl.ttc' # 'simsun.ttc' super().__init__(text=text, @@ -32,17 +33,28 @@ class UiSlider(Slider): ignore_paused=True, dynamic=True) # self.label.scale *= 8/10 + self.label.font = UrsinaConfig.CN_FONT self.knob.ignore_paused = True + + # self.label.collider = 'box' + # self.label.input = self.input # self.knob.text_entity.font = "" # self.knob.text_entity.scale *= 8/10 # self.height *= 8/10 + def input(self, key): + if self.hovered: + if key == "double click": + print("Slider->" + key) + self.value = self.default + class SwithButton(ButtonGroup): """ """ + def __init__(self, options, default, tooltips=None): super().__init__(options, min_selection=1, default=default, selected_color=color.rgba(0.1, 0.6, 0.1, 1.0), ignore_paused=True, @@ -63,6 +75,7 @@ class Buttons(ButtonGroup): """ """ + def __init__(self, options, default=None, tooltips=None): min_selection = len(options) super().__init__(options, min_selection=1, default=default, @@ -86,6 +99,7 @@ class UiButton(Button): """ """ + def __init__(self, text, on_click): super(UiButton, self).__init__(text=text, origin=(0, 0), y=2, on_click=on_click, color=color.rgba(0.0, 0.0, 0.0, 0.5), diff --git a/simulators/ursina/ursina_config.py b/simulators/ursina/ursina_config.py index 6c255f1f79e9c6634f061a56700108c3edb40fe5..81f8abf2f48e9825f88fd2dabd3a7eb0031e2dee 100644 --- a/simulators/ursina/ursina_config.py +++ b/simulators/ursina/ursina_config.py @@ -7,6 +7,7 @@ # python_version :3.8 # ============================================================================== # pip install -i http://pypi.douban.com/simple/ --trusted-host=pypi.douban.com ursina +from ursina import application class UrsinaConfig: @@ -51,6 +52,11 @@ class UrsinaConfig: cls.body_size_factor = 1.0 # 天体缩放的因子(不能太大,否则无法容得下大数量级的天体)调整 5e-7 最佳 cls.SCALE_FACTOR = 5e-7 + cls.time_scale_offset = 1.0 + + @classmethod + def get_app_time_scale(cls): + return cls.time_scale_offset * application.time_scale @property @classmethod diff --git a/simulators/ursina/ursina_event.py b/simulators/ursina/ursina_event.py index 68550a8a65bd07a124005225c522d02b684c8eba..b8de4f52fb73f0490eb95da4e629d5597319a792 100644 --- a/simulators/ursina/ursina_event.py +++ b/simulators/ursina/ursina_event.py @@ -20,12 +20,19 @@ class UrsinaEvent: return # 重启运行的订阅事件 UrsinaEvent.on_reset_funcs = [] + # 暂停运行的订阅事件 + UrsinaEvent.on_pause_funcs = [] + # 启动运行的订阅事件 + UrsinaEvent.on_start_funcs = [] + # 搜索天体的订阅事件 UrsinaEvent.on_searching_bodies_funcs = [] # 应用运行的订阅事件 UrsinaEvent.on_application_run_callback = [] - # + # 天体大小发生变化的订阅事件 UrsinaEvent.on_body_size_changed_callback = [] + # 计时器触发的订阅事件 + UrsinaEvent.on_timer_changed_callback = [] @staticmethod def on_body_size_changed_subscription(fun): @@ -36,6 +43,15 @@ class UrsinaEvent: for f in UrsinaEvent.on_body_size_changed_callback: f() + @staticmethod + def on_timer_changed_subscription(fun): + UrsinaEvent.on_timer_changed_callback.append(fun) + + @staticmethod + def on_timer_changed(evolve_dt): + for f in UrsinaEvent.on_timer_changed_callback: + f(evolve_dt) + @staticmethod def on_application_run_callback_subscription(fun): UrsinaEvent.on_application_run_callback.append(fun) @@ -53,6 +69,25 @@ class UrsinaEvent: for f in UrsinaEvent.on_reset_funcs: f() + @staticmethod + def on_start_subscription(fun): + UrsinaEvent.on_start_funcs.append(fun) + + @staticmethod + def on_start(): + for f in UrsinaEvent.on_start_funcs: + f() + + @staticmethod + def on_pause_subscription(fun): + UrsinaEvent.on_pause_funcs.append(fun) + + @staticmethod + def on_pause(): + for f in UrsinaEvent.on_pause_funcs: + f() + + @staticmethod def on_application_run(): if len(UrsinaEvent.on_application_run_callback) == 0: diff --git a/simulators/ursina_simulator.py b/simulators/ursina_simulator.py index 2ba408bbb05c24d1353fb02ad65eae4573d41833..df860d4a46c5d8b8b74cdcd717c3698b86cc217f 100644 --- a/simulators/ursina_simulator.py +++ b/simulators/ursina_simulator.py @@ -7,9 +7,7 @@ # python_version :3.8 # ============================================================================== # pip install -i http://pypi.douban.com/simple/ --trusted-host=pypi.douban.com ursina -from ursina import Ursina, window, Entity, Grid, Mesh, camera, Text, application, color, mouse, Vec2, Vec3, \ - load_texture, held_keys, distance, Audio, scene -from ursina.prefabs.first_person_controller import FirstPersonController +from ursina import Ursina, window, Entity, Grid, camera, application, color, distance, Audio import itertools from simulators.ursina.ursina_event import UrsinaEvent # from simulators.ursina.ursina_ui import UrsinaUI @@ -18,16 +16,13 @@ from simulators.ursina.ui.control_handler import ControlHandler from simulators.ursina.ursina_mesh import create_arrow_line from simulators.views.ursina_view import UrsinaView -from simulators.ursina.entities.ursina_player import UrsinaPlayer from simulators.ursina.ursina_config import UrsinaConfig from simulators.simulator import Simulator from common.system import System from common.func import find_file -import time import datetime -import math import os -from ursina import EditorCamera, PointLight, SpotLight, AmbientLight, DirectionalLight +from ursina import EditorCamera from sim_scenes.func import ursina_run @@ -141,8 +136,10 @@ class UrsinaSimulator(Simulator): time_scale = round(pow(max_distance, 1 / 4), 2) if time_scale < 0.01: time_scale = 0.01 - + # camera.scale + # sence application.time_scale = time_scale + UrsinaConfig.time_scale_offset = 1 / application.time_scale # UrsinaConfig.auto_scale_factor = 1.0e-9 def on_searching_bodies(self, **kwargs): @@ -174,10 +171,19 @@ class UrsinaSimulator(Simulator): else: # 配置中,每年、月、天等等有多少秒 evolve_dt = UrsinaConfig.seconds_per * run_speed_factor + + UrsinaEvent.on_timer_changed(evolve_dt) # interval_fator 能让更新天体运行状态(位置、速度)更精确 evolve_dt = evolve_dt * self.interval_fator super().evolve(evolve_dt) + + def create_timer(self): + from simulators.ursina.entities.timer import Timer + # 创建一个文本对象来显示计时器的时间 + self.timer = Timer() + return self.timer + def cosmic_background(self, texture='../textures/cosmic2.jpg'): """ 加入宇宙背景 @@ -216,6 +222,10 @@ class UrsinaSimulator(Simulator): if "view_closely" in kwargs: view_closely = kwargs["view_closely"] + show_timer = False + if "show_timer" in kwargs: + show_timer = kwargs["show_timer"] + if view_closely: # 近距离查看 if isinstance(view_closely, float): @@ -265,8 +275,12 @@ 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: + self.create_timer() EditorCamera(ignore_paused=True) # 防止打开中文输入法 @@ -301,7 +315,7 @@ class UrsinaSimulator(Simulator): if __name__ == '__main__': from bodies import Sun, Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, Moon from bodies.body import AU - from common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY, SECONDS_PER_HALF_DAY + from common.consts import SECONDS_PER_DAY """ 3个太阳、1个地球