提交 47ff8692 编写于 作者: 三月三net's avatar 三月三net

Python超人-宇宙模拟器

上级 ba7f7a13
# -*- 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
)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册