diff --git "a/docs/oumuamua/\350\277\220\350\241\214\350\275\250\350\277\271\345\222\214\346\227\266\351\227\264.jpg" "b/docs/oumuamua/\350\277\220\350\241\214\350\275\250\350\277\271\345\222\214\346\227\266\351\227\264.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..66b41d21280d52b93fc622dd1b89f9fdb4a113bb Binary files /dev/null and "b/docs/oumuamua/\350\277\220\350\241\214\350\275\250\350\277\271\345\222\214\346\227\266\351\227\264.jpg" differ diff --git a/sim_scenes/solar_system/halley_comet_sim_02.py b/sim_lab/halley_comet_sim_02.py similarity index 100% rename from sim_scenes/solar_system/halley_comet_sim_02.py rename to sim_lab/halley_comet_sim_02.py diff --git a/sim_scenes/solar_system/halley_comet_sim_03.py b/sim_lab/halley_comet_sim_03.py similarity index 100% rename from sim_scenes/solar_system/halley_comet_sim_03.py rename to sim_lab/halley_comet_sim_03.py diff --git a/sim_lab/oumuamua_sim.py b/sim_lab/oumuamua_sim.py new file mode 100644 index 0000000000000000000000000000000000000000..c86314a53892967c93a7174329fd79e5fe1c3c4b --- /dev/null +++ b/sim_lab/oumuamua_sim.py @@ -0,0 +1,488 @@ +# -*- coding:utf-8 -*- +# title :哈雷彗星场景半真实模拟 +# description :哈雷彗星运行轨道使用了万有引力,其他天体使用 astropy 包的真实数据 +# author :Python超人 +# date :2023-10-28 +# link :https://gitcode.net/pythoncr/ +# python_version :3.9 +# ============================================================================== +from dataclasses import dataclass +from dataclasses import field +from ursina import camera, application, lerp, Vec3 + +from common.celestial_data_service import init_bodies_reality_pos_vels, conv_to_astropy_time, \ + set_solar_system_celestial_position +from common.consts import SECONDS_PER_YEAR, AU +from common.func import calculate_distance +from bodies import Earth +from sim_scenes.func import create_text_panel, camera_look_at, get_run_speed_factor, \ + camera_move_update, camera_move_to_target_update, camera_move_control +from sim_scenes.func import ursina_run, create_sphere_sky +from simulators.ursina.entities.body_timer import TimeData +from simulators.ursina.entities.entity_utils import get_value_direction_vectors +from simulators.ursina.ui.control_ui import ControlUI +from simulators.ursina.ursina_config import UrsinaConfig +from simulators.ursina.ursina_event import UrsinaEvent +from simulators.ursina.ursina_mesh import create_label + + +@dataclass(order=True) +class OumuamuaParams: + start_time: str = field(default='1983-03-20 00:00:00') + init_velocity: list[float] = field(default_factory=[3.34, 0, 10.718]) + init_position: list[float] = field(default_factory=[0, 0.5 * AU, -10 * AU]) + + +class OumuamuaSim: + """ + 星际访客奥陌陌 + 重要时间点: + + """ + + def __init__(self, _params=None): + # super(OumuamuaParams, self).__init__() + if _params is None: + self.params = OumuamuaParams() + else: + self.params = _params + + if isinstance(_params.start_time, str): + self.start_time = conv_to_astropy_time(_params.start_time) + else: + self.start_time = _params.start_time + + # print("北京时间:", dt.to_datetime(timezone=pytz.timezone('Asia/Shanghai'))) + + def build(self): + """ + 构建太阳系系统以及哈雷彗星 + @return: + """ + self.build_solar_system(ignore_gravity=True, start_time=self.start_time) + self.sun.glows = (2, 1.005, 0.01) + # 创建哈雷彗星创建哈雷彗星 + self.halley_comet = create_halley_comet(self.params.init_velocity, self.params.init_position) + + self.bodies.append(self.halley_comet) + + # from objs import CameraTarget + # + # self.bodies.append(CameraTarget(size_scale=2e8, init_position=[0, 0, 0], color=(255, 0, 0))) + # self.bodies.append(CameraTarget(size_scale=2e8, init_position=[5 * AU, 0, 0], color=(255, 255, 0))) + + # print(CameraTarget.targets) + + def init_settings(self): + """ + 初始化设置 + @return: + """ + from ursina import color + # 创建天空 + create_sphere_sky(scale=50000) + # UrsinaConfig.trail_type = "curve_line" + # UrsinaConfig.trail_length = 300 + UrsinaConfig.trail_type = "line" + # UrsinaConfig.trail_length = 152 # 尾巴数量刚刚好 + UrsinaConfig.trail_length = 130 + UrsinaConfig.trail_thickness_factor = 3 + + # UrsinaConfig.trail_length = 180 + UrsinaConfig.trail_factor = 3 + + # camera.clip_plane_near = 0.1 + camera.clip_plane_far = 51000 + # camera.fov = 60 + + # self.sun.planet.color = color.white + + # application.time_scale = 0.01 + # 摄像机移动 update + # camera_move_update() + # camera_move_to_target_update() + + def create_orbit_lines(self): + """ + 创建太阳系天体的真实轨迹(太阳和哈雷彗星除外) + @return: + """ + self.orbit_lines = [] + for body in self.bodies[1:]: + if isinstance(body, Earth): + alpha = 0.5 + else: + alpha = 0.2 + orbit_line = create_orbit_line(self.sun, body, self.start_time, alpha=alpha) + if orbit_line is not None: + self.orbit_lines.append(orbit_line) + + def set_bodies_position(self, time_data: TimeData): + """ + 设置天体的位置(包含速度和加速度的信息) + @param time_data: + @return: + """ + t = self.start_time + time_data.total_days + set_solar_system_celestial_position(self.bodies, t, False) + + def create_year_label(self, trail, year, + halley_comet_pos=None, + pos=None, + label_color=None, + scale=40, + background=False): + """ + 在界面上创建年份的标签 + @param trail: + @param year: + @param halley_comet_pos: + @return: + """ + # 为了不影响 2023年12月9日的显示,附近的 Label 就不显示 + # if year in ["1986", "2061", "2023"]: + # return + + if trail is None: + _pos = halley_comet_pos + else: + _pos = pos + if _pos is None: + _pos = (0, 0, 0) + + if label_color is None: + label_color = (255, 255, 255, 255) + + label = create_label(trail, label=year, pos=_pos, + label_color=label_color, + font="fonts/DroidSansFallback.ttf", + scale=scale, alpha=1.0, background=background + ) + label.set_light_off() + # 只记录年 + if len(str(year)) == 4: + self.last_year = year + + def update_comet_trail_alpha(self, distance_sun): + """ + 根据彗哈雷星和太阳的距离,设置彗星尾巴的透明度来模仿接近太阳有慧尾,离开太阳到一定距离就渐渐消失 + @param distance_sun: 彗哈雷星和太阳的距离 + @return: + """ + # 距离转为天文单位 + d_au = distance_sun / AU + # 渐渐消失的距离范围(开始消失距离, 完全消失距离) + HIDE_DISTANCE = 3, 12 + # 彗星最大的透明度 + MAX_ALPHA = 0.8 + + # 大于完全消失距离 + if d_au >= HIDE_DISTANCE[1]: + alpha = 0 + elif HIDE_DISTANCE[1] > d_au > HIDE_DISTANCE[0]: # 渐渐消失的距离范围内,通过距离值大小确定透明度(慢慢消失的效果) + alpha = MAX_ALPHA - (d_au - HIDE_DISTANCE[0]) / (HIDE_DISTANCE[1] - HIDE_DISTANCE[0]) * MAX_ALPHA + else: + alpha = MAX_ALPHA + # 修改彗星尾巴的透明度 + self.halley_comet.planet.comet_trail.set_alpha(alpha) + # self.halley_comet.planet.comet_sphere.alpha = alpha + + def show_milestone_lable(self, last_trail, dt): + """ + 远日点: 35.1 AU(2023年12月9日) + 近日点: 0.586 AU 上次通过近日点:1986年2月9日 下次通过近日点:2061年7月28日 + @param last_trail: + @param dt: + @return: + """ + milestones = [("1986-02-09", (0, 2, 0)), ("2023-12-09", (0, 0, -3)), ("2061-07-28", (0, 3, 0))] + for milestone, pos in milestones: + prop_name = f"milestone_{milestone}" + if not hasattr(self, prop_name) and dt.strftime("%Y-%m-%d") >= milestone: + setattr(self, prop_name, milestone) + if pos is None: + pos = (0, 2, 0) + self.create_year_label(last_trail, milestone, label_color=(0, 255, 0), + pos=pos, scale=60, + background=True) + # application.paused = True + # UrsinaEvent.on_pause() + # ControlUI.current_ui.on_off_switch.on_value_changed() + + def s_f(self, value=1): + if value == 0: + return 0 + return get_run_speed_factor() * value + + def camera_move(self, dt): + """ + 摄像机移动控制 + @param dt: + @return: + """ + # 摄像机移动控制数据 + camera_move_infos = [ + # 条件:年份 + # 移动的信息: + # 按坐标系方向移动 x:右+左-, y:升+降-, z:前+(接近太阳)后-(远离太阳) + # 以摄像机视角移动 f:前 b:后 l:左 r:右 u:上 d:下 + (1982, {"x": 2, "y": 1}), + # (1983, {"to": {"ct_id": 1, "t": 10}}), + (1986, {"x": 2, "y": -2, "z": -15}), + (1987, {"y": -6, "z": -12}), + (1988, {"y": -3, "z": -12}), + (1989, {"z": -8, "f": -5}), + (1993, {"z": -8, "f": -3}), + (1995, {"z": -8}), + (2000, {"z": -8, "y": -0.2}), + (2013, {}), + (2048, {"f": 3}), + (2062, {"y": -3}), + (2063, {"y": -10, "z": 2}), + (2181, {}), + # (2082, {"exit": True}) + ] + + camera_move_control(camera_move_infos, + cond_cb=lambda ps: ps["next_cond"] > dt.year >= ps["cond"], + value_conv=self.s_f, smooth=10) + + def check_create_year_label(self, last_trail, dt): + """ + 检测并创建年标签 + @param last_trail: + @param dt: + @return: + """ + if last_trail is None: + return + + import copy + year = dt.strftime("%Y") + pos = self.halley_comet.planet.position + # 里程碑标签 + if self.show_milestone_lable(last_trail, dt): + pass + elif not hasattr(self, "last_year"): + # 第一次运行,则创建“年标签” + self.create_year_label(last_trail, year, pos) + self.last_label_pos = copy.deepcopy(self.halley_comet.position) + elif self.last_year != year: + if hasattr(self, "last_label_pos"): + # 防止“年标签”显示非常紧密 + d = calculate_distance(self.halley_comet.position, self.last_label_pos) + if d > 2 * AU: + self.create_year_label(last_trail, year, pos) + self.last_label_pos = copy.deepcopy(self.halley_comet.position) + + def update_halley_comet_info(self, dt): + """ + 更新哈雷彗星的信息 + @param dt: + @return: + """ + # 哈雷彗星面向太阳,这样彗尾就远离太阳的方向 + self.halley_comet.planet.look_at(self.sun.planet) + + # 计算哈雷彗星与太阳、地球的距离 + d_sun = calculate_distance(self.halley_comet.position, self.sun.position) + d_earth = calculate_distance(self.halley_comet.position, self.earth.position) + # 哈雷彗星所有轨迹线(注意不是彗尾) + trail_keys = self.halley_comet.planet.trails.keys() + # 哈雷彗星有轨迹线 + if len(trail_keys) > 0: + # 找的最后的轨迹线 + last_trail = list(trail_keys)[-1] + if hasattr(last_trail, "entity_infos"): + # 如果是轨迹球,则轨迹球会记录当前位置与太阳、地球的距离,并记录了当前的日期 + last_trail.entity_infos["distance_from_sun"] = d_sun + last_trail.entity_infos["distance_from_earth"] = d_earth + last_trail.entity_infos["time"] = dt.strftime("%Y-%m-%d") + + # 检测并再轨迹线上创建年标签 + self.check_create_year_label(last_trail, dt) + + # 更新彗星尾巴的透明度 + self.update_comet_trail_alpha(d_sun) + + # 计算和更新近日点数据 + self.update_comet_peri(d_sun, dt) + # 计算和更新远日点数据 + self.update_comet_aphel(d_sun, dt) + # 更新文字信息面板 + self.update_text_panel(d_sun) + + def update_text_panel(self, d_sun): + """ + 更新文字信息面板 + @param d_sun: + @return: + """ + panel_text = "哈雷彗星:\n\n当前日距:%s AU" % "{:.3f}".format(d_sun / AU).rjust(6, "0") + panel_text += "\n\n最大日距:%s AU" % "{:.3f}".format(self.comet_aphel / AU).rjust(6, "0") + panel_text += "\n\n最小日距:%s AU" % "{:.3f}".format(self.comet_peri / AU).rjust(6, "0") + velocity, _ = get_value_direction_vectors(self.halley_comet.velocity) + panel_text += "\n\n当前速度:%s km/s" % "{:.3f}".format(velocity).rjust(6, "0") + + self.text_panel.text = panel_text + + def update_comet_aphel(self, d_sun, dt): + """ + 计算和更新远日点数据 + @param d_sun: + @param dt: + @return: + """ + # 哈雷彗星离太阳最远的点称为 "aphelion of Halley's Comet"(远日点) + if not hasattr(self, "comet_aphel"): + self.comet_aphel = d_sun + self.comet_aphel_dt = dt.strftime("%Y-%m-%d") + elif d_sun > self.comet_aphel: + self.comet_aphel = d_sun + self.comet_aphel_dt = dt.strftime("%Y-%m-%d") + + def update_comet_peri(self, d_sun, dt): + """ + 计算和更新近日点数据 + @param d_sun: + @param dt: + @return: + """ + # 哈雷彗星离太阳最近的点称为 "perihelion of Halley's Comet"(近日点:comet_peri), + if not hasattr(self, "comet_peri"): + self.comet_peri = d_sun + self.comet_peri_dt = dt.strftime("%Y-%m-%d") + elif d_sun < self.comet_peri: + self.comet_peri = d_sun + self.comet_peri_dt = dt.strftime("%Y-%m-%d") + + def update_ui(self, time_data, dt): + """ + 更新UI,包含天体的位置、时钟、轨道的位置 + @param time_data: + @param dt: + @return: + """ + if dt.year > 2041: + UrsinaConfig.trail_length = 210 + # 更新天体的位置 + self.set_bodies_position(time_data) + # # 更新时钟 + # self.show_clock(dt) + # 更新轨道的位置(TODO:短时间可以忽略) + # for i, orbit_line in enumerate(self.orbit_lines): + # orbit_line.position = self.sun.planet.position + + def on_ready(self): + """ + 事件绑定后,模拟器运行前会触发 + @return: + """ + # 初始化设置 + self.init_settings() + + # self.set_window_size((int(1920 * r), int(1080 * r)), False) + # 最大分辨率的高度-1,保证不全屏 + self.set_window_size((1920, 1079), False) + # self.set_window_size((1920 , 1080 ), False) + # 显示网格以及坐标线 + # self.show_grid_axises() + + # 创建太阳系天体的真实轨迹(太阳和哈雷彗星除外) + self.create_orbit_lines() + # 创建信息显示面板 + # self.text_panel = create_text_panel(font="fonts/sanjixiaozhuanti.ttf", font_scale=1.5) + self.text_panel = create_text_panel(font="fonts/DroidSansFallback.ttf", font_scale=1.3) + + def on_timer_changed(self, time_data): + """ + + @param time_data: + @return: + """ + dt = time_data.get_datetime(self.start_time) + # 摄像机看向哈雷彗星 + camera_look_at(self.halley_comet, rotation_z=0) + # camera.look_at_2d(self.halley_comet.planet.position, axis='y') + + time_total_hours = time_data.total_hours + + # if not hasattr(self, "last_total_hours"): + # self.last_total_hours = time_total_hours + # self.update_halley_comet_info(dt) + # print("time_total_hours", time_total_hours) + # # 摄像机移动每10小时更新一次 + # self.interval_run(fun=self.camera_move, fun_args=[dt], + # total_times=time_total_hours, + # interval=20) + self.camera_move(dt) + + if 2040 > dt.year > 1987: + update_halley_comet_info_interval = 400 + update_clock_interval = 400 + else: + update_halley_comet_info_interval = 33 + update_clock_interval = 60 + + # 更新哈雷彗星信息是每20个小时更新一次(哈雷彗星姿态、哈雷彗星距离信息) + self.interval_run(fun=self.update_halley_comet_info, fun_args=[dt], + total_times=time_total_hours, + interval=update_halley_comet_info_interval) + + # 每50个小时更新一次界面(天体位置) + self.interval_run(fun=self.update_ui, fun_args=[time_data, dt], + total_times=time_total_hours, + interval=60) + + self.interval_run(fun=self.show_clock, fun_args=[dt], + total_times=time_total_hours, + interval=update_clock_interval) + + # # 每隔一段时间运行一次更新(不要太频繁更新,会导致摄像机抖动) + # if time_total_hours - self.last_total_hours > 50: + # self.update_halley_comet_info(dt) + # self.update_ui(time_data, dt) + # # 记录最后更新的总耗时(小时) + # self.last_total_hours = time_total_hours + + +if __name__ == '__main__': + """ + 哈雷彗星场景模拟 + """ + # 远日点: 35.1 AU(2023年12月9日) + # 近日点: 0.586 AU 上次通过近日点:1986年2月9日 下次通过近日点:2061年7月28日 + # 2019年5月6日 34.772 + params = OumuamuaParams( + start_time='1982-09-24 00:00:00', + # init_velocity=[-2.80, 5.10, 8.65], # 1/8 + init_velocity=[-2.774, 5.126, 8.65], # 1/8 + init_position=[0, -5 * AU, -10 * AU] + ) + + sim = OumuamuaSim(params) + sim.build() + + # 订阅事件后,上面2个函数功能才会起作用 + # 运行中,每时每刻都会触发 on_timer_changed + UrsinaEvent.on_timer_changed_subscription(sim.on_timer_changed) + # 运行前会触发 on_ready + UrsinaEvent.on_ready_subscription(sim.on_ready) + # 使用 ursina 查看的运行效果 + # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 + # position = 左-右+、上+下-、前+后- + ursina_run(sim.bodies, + SECONDS_PER_YEAR * get_run_speed_factor(), + # position=(0, 2 * AU, -11 * AU), + # position=(0, 0.5 * AU, -5 * AU), + # position=(2 * AU, -5 * AU, -20 * AU), + position=(2 * AU, -6 * AU, -20 * AU), + cosmic_bg='', + show_trail=True, + # bg_music='sounds/no_glory.mp3', + show_camera_info=False, + # video_recoder=True, + show_control_info=False, + timer_enabled=True, + show_grid=False + )