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

Python超人-宇宙模拟器

上级 6c0744a4
# -*- coding:utf-8 -*-
# title :模拟太阳系给天体真实时间和位置
# description :模拟太阳系给天体真实时间和位置
# author :Python超人
# date :2023-07-23
# link :https://gitcode.net/pythoncr/
# python_version :3.8
# ==============================================================================
import numpy as np
from bodies import Sun, Mercury, Venus, Earth, Mars, Asteroids, Jupiter, Saturn, Uranus, Neptune, Moon
from common.celestial_data_service import get_body_posvel, recalc_moon_position, calc_solar_acceleration, \
set_solar_system_celestial_position, set_earth_rotation, get_reality_orbit_points
from common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY, SECONDS_PER_HOUR, AU
from sim_scenes.func import ursina_run, create_sphere_sky, create_text_panel
from sim_scenes.universe_sim_scenes import UniverseSimScenes
from simulators.func import ext_fun_for_method
from simulators.ursina.entities.body_timer import TimeData
from simulators.ursina.ui.control_ui import ControlUI
from simulators.ursina.ursina_config import UrsinaConfig
from simulators.ursina.ursina_event import UrsinaEvent
from ursina import camera, application
from simulators.ursina.ursina_mesh import create_orbit_line, create_orbit_by_points
class SolarSystemRealitySim(UniverseSimScenes):
"""
以:武汉江城明珠豪生大酒店上面的球为太阳。直径:35米
"""
def __init__(self):
"""
@param debug_mode: 是否为调试模式
"""
self.show_asteroids = False
self.clock_position_center = False
self.show_earth_clouds = False
self.debug_mode = False
self.recalc_moon_pos = True
def create_bodies(self):
"""
创建太阳系的天体
@return:
"""
# 由于宇宙空间尺度非常大,如果按照实际的天体大小,则无法看到天体,因此需要对天体的尺寸进行放大
# 太阳 60倍、
SUN_SIZE_SCALE = 50
SIZE_SCALE_1 = 2000
SIZE_SCALE_2 = 500
SIZE_SCALE_3 = 1500
# 太阳缩放比例
self.sun_size_scale = 0.04e2 if self.debug_mode else SUN_SIZE_SCALE
# 地月缩放比例
# 为了更好的展示效果,需要对月球的位置重新计算(使得地月距离放大,月球相对地球方向不变),重新计算位置后,地球和月球可以放大1000倍以上
if self.recalc_moon_pos: # 重新计算月球位置
self.earth_size_scale = 10e3 if self.debug_mode else 1e3
self.moon_size_scale = 2e3
else:
# 不重新计算,则地月的距离相对整个太阳系会非常近,因此,月球只放大了10倍
self.earth_size_scale = 1e1
self.moon_size_scale = 1e1
self.sun = Sun(name="太阳", size_scale=self.sun_size_scale) # 太阳
self.sun.sim_d = 3500 # 35米 = 3500厘米
self.sun.glows = None
self.mercury = Mercury(name="水星", size_scale=SIZE_SCALE_1) # 水星
self.venus = Venus(name="金星", size_scale=SIZE_SCALE_1) # 金星
self.earth = Earth(name="地球", texture="earth_hd.jpg",
rotate_angle=3.44,
size_scale=SIZE_SCALE_1) # 地球
self.earth_clouds = Earth(name="地球云层", texture="transparent_clouds.png", show_trail=False,
rotate_angle=3.44,
size_scale=SIZE_SCALE_1 * 1.01) # 地球云层
# self.moon = Moon(name="月球", size_scale=self.moon_size_scale) # 月球
self.mars = Mars(name="火星", size_scale=SIZE_SCALE_1) # 火星
# self.asteroids = Asteroids(size_scale=1e2, parent=self.sun, rotate_angle=-20) # 模拟的小行星带
self.jupiter = Jupiter(name="木星", size_scale=SIZE_SCALE_2) # 木星
self.saturn = Saturn(name="土星", size_scale=SIZE_SCALE_2) # 土星
self.uranus = Uranus(name="天王星", size_scale=SIZE_SCALE_3) # 天王星
self.neptune = Neptune(name="海王星", size_scale=SIZE_SCALE_3) # 海王星
# 行星
self.planets = [self.mercury, self.venus, self.earth, self.mars,
self.jupiter, self.saturn, self.uranus, self.neptune]
# 所有天体
self.bodies = [self.sun] + self.planets # + [self.moon]
sim_d_r = self.sun.diameter / self.sun.sim_d
for body in self.bodies:
# print("%s的直径相当于:%.2fcm" % (body.name, body.diameter / sim_d_r))
print("%s的位置相当于:%.2fkm" % (body.name, body.position[2] / (sim_d_r*100*1000)))
# 太阳的直径相当于:3500.00cm
# 水星的直径相当于:12.27cm
# 金星的直径相当于:30.44cm 排球、足球、篮球的直径分别是25~28厘米、21.96~22.04厘米、24.6厘米。
# 地球的直径相当于:32.05cm 排球、足球、篮球的直径分别是25~28厘米、21.96~22.04厘米、24.6厘米。
# 火星的直径相当于:17.04cm
# 木星的直径相当于:351.55cm
# 土星的直径相当于:292.82cm
# 天王星的直径相当于:127.54cm
# 海王星的直径相当于:123.80cm
# 太阳的位置相当于:0.00km 武汉江城明珠豪生大酒店 35米
# 水星的位置相当于:1.44km 武汉科学技术馆 武汉歌舞剧院 武汉剧院 武汉市中心医院
# 金星的位置相当于:2.71km 龙王庙 徐家棚 不到中山公园
# 地球的位置相当于:3.76km 超过中山公园 沙湖公园 南岸嘴江滩公园
# 火星的位置相当于:5.72km 琴台大剧院 楚河汉街 首义广场
# 木星的位置相当于:19.52km 后官湖 汤逊湖
# 土星的位置相当于:35.73km 西:武汉野生动物王国
# 天王星的位置相当于:72.21km 西:仙桃
# 海王星的位置相当于:115.46km 西:天门 北:大悟 东:大希 南:赤壁
if self.show_earth_clouds:
self.bodies += [self.earth_clouds]
# if self.show_asteroids:
# self.bodies += [self.asteroids]
exit()
def init_earth(self):
"""
初始化地球
@return:
"""
# 让地球显示自转轴线
# self.earth.rotate_axis_color = (255, 255, 50)
# 如果为调试模式,则太阳光对地球无效,方便查看
if self.debug_mode:
self.earth.set_light_disable(True)
def show_clock(self, dt):
"""
显示时钟
@param dt: 时间 datetime
@return:
"""
if self.clock_position_center:
position, origin = (0, .25), (0, 0),
else:
from ursina import window
aspect_ratio = window.aspect_ratio
position, origin = (0.5 * aspect_ratio - 0.3, -0.465), (-0.5, 0.5),
# position, origin = (0.60, -0.465), (-0.5, 0.5),
ControlUI.current_ui.show_message(dt.strftime('%Y-%m-%d %H:%M:%S'),
position=position,
origin=origin,
font="verdana.ttf",
close_time=-1)
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, self.recalc_moon_pos)
def on_ready(self):
"""
事件绑定后,模拟器运行前会触发
@return:
"""
# 运行前触发
self.set_window_size((1919, 1080), False)
self.sky = create_sphere_sky(scale=80000)
self.create_orbit_lines()
camera.clip_plane_near = 0.1
camera.clip_plane_far = 100000
# camera.rotation_z = -20
if self.debug_mode:
camera.fov = 30 # 调试时,拉近摄像机距离
# 需要按照时间和日期来控制地球的自转,所以删除控制地球自转的属性
delattr(self.earth.planet, "rotation_speed")
delattr(self.earth.planet, "rotspeed")
# 设置后,可以调整鼠标键盘的控制速度
application.time_scale = 5
self.text_panel = create_text_panel(font="fonts/DroidSansFallback.ttf", font_scale=1.3)
self.text_panel.parent.scale_y /= 6
self.text_panel.scale_y *= 6
def update_text_panel(self):
"""
更新文字信息面板
@param d_sun:
@return:
"""
from ursina import distance
d_sun = distance(self.sun.planet.position, camera.position)
d_sun = d_sun / UrsinaConfig.SCALE_FACTOR / AU
panel_text = "当前日距:%s AU" % "{:.2f}".format(d_sun) # .rjust(6, "0")
self.text_panel.text = panel_text
def on_timer_changed(self, time_data: TimeData):
"""
事件绑定后,时时刻刻都会触发
@param time_data:
@return:
"""
dt = time_data.get_datetime(str(self.start_time))
# 设置天体的位置(包含速度和加速度的信息)
self.set_bodies_position(time_data)
# 保证地球的自转和北京时间同步
set_earth_rotation(self.earth, dt)
# 显示时钟
# self.show_clock(dt)
self.update_text_panel()
def bind_events(self):
# 运行中,每时每刻都会触发 on_timer_changed
UrsinaEvent.on_timer_changed_subscription(self.on_timer_changed)
# 运行前会触发 on_ready
UrsinaEvent.on_ready_subscription(self.on_ready)
def create_orbit_line(self, center_body, body, start_time, alpha=0.2):
import math
if not hasattr(body, "orbital_days"):
return None
orbital_days = int(math.ceil(body.orbital_days))
points = get_reality_orbit_points(type(body).__name__.lower(),
start_time=start_time,
days=orbital_days,
segments=100)
# print(points)
orbit_line = create_orbit_by_points(center_body.position, points, line_color=body.trail_color,
alpha=alpha, thickness=1)
return orbit_line
def create_orbit_lines(self):
"""
创建太阳系天体的真实轨迹(太阳和哈雷彗星除外)
@return:
"""
self.orbit_lines = []
for body in self.bodies[1:]:
alpha = 1
orbit_line = self.create_orbit_line(self.sun, body, self.start_time, alpha=alpha)
if orbit_line is not None:
self.orbit_lines.append(orbit_line)
def run(self,
debug_mode=False,
start_time=None,
dt=None,
# show_asteroids=False,
show_earth_clouds=False,
recalc_moon_pos=True,
clock_position_center=False):
"""
模拟运行
@param debug_mode: 是否调试模式
@param start_time: 运行的开始时间
@param dt: 运行速度(dt的值为秒数,表示1秒相当于dt的秒数)
@param show_earth_clouds: 地球是否显示云层(图片效果,不是真实的云层)
@param recalc_moon_pos: 为了更好的展示效果,需要对月球的位置重新计算(使得地月距离放大,月球相对地球方向不变)
@param clock_position_center: 时钟是否显示在中间
@return:
"""
self.recalc_moon_pos = recalc_moon_pos
self.debug_mode = debug_mode
self.clock_position_center = clock_position_center
# self.show_asteroids = show_asteroids
self.show_earth_clouds = show_earth_clouds
# 创建太阳系天体
self.create_bodies()
# glows = (glow_num:10, glow_scale:1.03 glow_alpha:0.1~1)
# self.sun.glows = (4, 1.005, 0.1)
# 对地球进行初始化
self.init_earth()
# 绑定事件
self.bind_events()
from astropy.time import Time
from datetime import datetime
# 开始时间为空,则默认为当前时间
if start_time is None:
self.start_time = Time.now() # 获取默认开始时间为当前时间
elif isinstance(start_time, str):
self.start_time = Time(datetime.strptime(start_time + '+0800', '%Y-%m-%d %H:%M:%S%z'),
format='datetime')
# from common.image_utils import find_texture
# self.sky_texture = find_texture("bg_pan.jpg", None)
# if self.sky_texture is None:
# cosmic_bg = None
# else:
# cosmic_bg = ''
if dt is None:
dt = 1 # 1秒=1秒
self.init_steps()
# 使用 ursina 查看的运行效果
# 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹
# position = 左-右+、上+下-、前+后-
ursina_run(self.bodies, dt,
position=(0, 0.2 * AU, -3 * AU),
gravity_works=False, # 关闭万有引力的计算
show_grid=False,
cosmic_bg='',
show_camera_info=False,
show_exit_button=False,
show_control_info=False,
timer_enabled=True)
def init_steps(self):
self.step_index = 0
self.steps = [
self.set_bodies_as_real_scale,
lambda: self.enabled_orbit_lines(),
lambda: self.recover_body_scale(self.sun),
lambda: self.recover_body_scale([self.earth, self.mercury, self.venus, self.mars, self.earth_clouds]),
lambda: self.recover_body_scale([self.jupiter, self.saturn]),
lambda: self.recover_body_scale([self.uranus, self.neptune]),
lambda: self.recover_sky(),
# lambda: self.recover_run(),
self.set_bodies_as_real_scale_animation
]
def enabled_orbit_line(self, orbit_line):
from ursina import Vec4
orbit_line.alpha = 0
orbit_line.color *= Vec4(1, 1, 1, 0)
# orbit_line.origin_color = orbit_line.color
def orbit_line_update():
alpha = orbit_line.alpha
alpha += 0.01
print(alpha)
if alpha > orbit_line.origin_alpha:
alpha = orbit_line.origin_alpha
orbit_line.update = lambda: None
orbit_line.alpha = alpha
orbit_line.color = Vec4(orbit_line.color[0], orbit_line.color[1], orbit_line.color[2], alpha)
print(orbit_line, orbit_line.alpha)
orbit_line.update = orbit_line_update
orbit_line.enabled = True
def enabled_orbit_lines(self):
for orbit_line in self.orbit_lines:
# orbit_line.origin_alpha = orbit_line.alpha
# orbit_line.alpha = 0
self.enabled_orbit_line(orbit_line)
def scale_animation(self, body):
size_scale, scale_x = self.body_scale_dict[body]
scale_inc = (scale_x - body.planet.scale_x) / 200.0
def update_scale():
# body.planet.origin_update()
planet_scale_x = body.planet.scale_x
planet_scale_x += scale_inc
if planet_scale_x > scale_x:
planet_scale_x = scale_x
body.planet.update = body.planet.origin_update
body.planet.scale = planet_scale_x
body.planet.update = update_scale
def recover_run(self):
pass
# for body in self.bodies:
# body.planet.update = body.planet.origin_update
def recover_sky(self):
def update_sky():
alpha = self.sky.alpha
alpha += 0.01
if alpha > 1.0:
alpha = 1.0
self.sky.update = lambda: None
self.sky.alpha = alpha
self.sky.update = update_sky
def recover_body_scale(self, body_or_bodies):
if not isinstance(body_or_bodies, list):
bodies = [body_or_bodies]
else:
bodies = body_or_bodies
for body in bodies:
self.scale_animation(body)
from ursina import invoke
def recover_run_speed():
UrsinaConfig.run_speed_factor = 1
invoke(function=recover_run_speed, delay=3.0)
def set_body_as_real_scale(self, body):
UrsinaConfig.run_speed_factor = 0.01
body.planet.origin_update = body.planet.update
# body.planet.update = lambda: None
def update_scale():
size_scale, scale_x = self.body_scale_dict[body]
body.planet.scale = scale_x / size_scale
ext_fun_for_method(body.planet, after_run_fun=update_scale)
def set_body_as_real_scale_animation(self, body):
UrsinaConfig.run_speed_factor = 0.01
body.planet.origin_update = body.planet.update
size_scale, scale_x = self.body_scale_dict[body]
scale_inc = (scale_x - scale_x / size_scale) / 200.0
# scale_inc = (scale_x / size_scale) / 10.0
def update_scale():
planet_scale_x = body.planet.scale_x
planet_scale_x -= scale_inc
if planet_scale_x <= scale_x / size_scale:
planet_scale_x = scale_x / size_scale
body.planet.update = lambda: None # body.planet.origin_update
body.planet.scale = planet_scale_x # scale_x / size_scale
body.planet.update = update_scale
# ext_fun_for_method(body.planet, after_run_fun=update_scale)
def set_bodies_as_real_scale(self):
self.save_body_scale()
for body in self.bodies:
self.set_body_as_real_scale(body)
for orbit_line in self.orbit_lines:
orbit_line.origin_alpha = orbit_line.alpha
# orbit_line.alpha = 0
orbit_line.enabled = False
self.sky.alpha = 0
def set_bodies_as_real_scale_animation(self):
# self.save_body_scale()
for body in self.bodies:
self.set_body_as_real_scale_animation(body)
# for orbit_line in self.orbit_lines:
# orbit_line.origin_alpha = orbit_line.alpha
# # orbit_line.alpha = 0
# orbit_line.enabled = False
self.sky.alpha = 0
def save_body_scale(self):
self.body_scale_dict = {}
for body in self.bodies:
self.body_scale_dict[body] = (body.size_scale, body.planet.scale_x)
print(self.body_scale_dict)
def input(self, key):
# print(key)
if key == "enter up":
if self.step_index < len(self.steps):
fun = self.steps[self.step_index]
fun()
self.step_index += 1
if __name__ == '__main__':
# 以下展示的效果为太阳系真实的时间和位置
sim = SolarSystemRealitySim()
def input(key):
sim.input(key)
sim.run(
# debug_mode=True, # 是否调试模式
dt=SECONDS_PER_DAY, # 1秒=1天
# dt=SECONDS_PER_HOUR, # 1秒=1小时
start_time='1985-02-28 00:00:00',
# start_time='2025-01-01 00:00:00',
# start_time='2050-01-01 12:00:00', # 指定运行的开始时间,不指定为当前时间
# 网上没有找到精确的日期,宇宙模拟器展示大概2040年8、9月份
# start_time='2040-08-15 12:00:00', # 金木水火土五星连珠的时间 # https://baijiahao.baidu.com/s?id=1776120995339598449
# start_time='2049-01-01 12:00:00', # 九星连珠的时间 # https://988892.com/qiwenyishi/2023061960711.html
# start_time='2149-12-10 12:00:00', # 九星连珠的时间 # https://baijiahao.baidu.com/s?id=1654160345900112362
show_earth_clouds=True, # 地球是否显示云层(图片效果,不是真实的云层)
# recalc_moon_pos=False, # 为了更好的展示效果,需要对月球的位置重新计算(使得地月距离放大,月球相对地球方向不变)
clock_position_center=True # 时钟是否显示在中间
)
......@@ -55,7 +55,7 @@ def create_space_station(mars_radius):
space_station = ScifiSpaceStation(name="空间站",
size_scale=0.5,
# init_position=[0.46 * mars_radius, 0, -3.55e4],
init_position=[0.46 * mars_radius, 300, -4.40e4],
init_position=[0.46 * mars_radius, 300, -5.1e4],
init_velocity=[0, 0, 0]) \
.set_ignore_gravity(True)
return space_station
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册