# -*- coding:utf-8 -*- # title :模拟场景 # description :模拟场景 # author :Python超人 # date :2024-03-20 # link :https://gitcode.net/pythoncr/ # python_version :3.9 # ============================================================================== from ursina import Ursina, camera, EditorCamera, Sky, Entity, load_model, color from common.celestial_data_service import set_solar_system_celestial_position, conv_to_astropy_time, \ get_reality_orbit_points, get_body_posvel, recalc_moon_position, calc_solar_acceleration from common.consts import SECONDS_PER_DAY, AU from bodies.body import Body from bodies import * from sim_scenes.func import create_sphere_sky, ursina_run from sim_scenes.universe_sim_scenes import UniverseSimScenes 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 simulators.ursina.ursina_mesh import create_orbit_by_points from 引力世界.数据.天体数据 import get_body_pos_vel import numpy as np 关闭=False 打开=True 无=None class 模拟配置: 显示轨道 = 关闭 地月距离倍数 = 1.0 中心天体=无 class 模拟场景(UniverseSimScenes): def 配置(self, 配置项: 模拟配置): pass def __init__(self): # self.app = Ursina() # # self.sky = create_sphere_sky(scale=80000) self.造物() # # EditorCamera() self.配置项 = 模拟配置() self.配置(self.配置项) def get_body_pos_vel(self, body): pos, vel = get_body_pos_vel(body) return pos, vel def 获取天体列表(self): for p in vars(self): f = getattr(self, p) if isinstance(f, Body): yield f def 获取坐标和速度(self, 天体名称): pos, vel = self.get_body_pos_vel(天体名称) if not self.配置项.中心天体 is None: center_pos, _ = self.get_body_pos_vel(self.配置项.中心天体.名称) pos = pos - center_pos return pos, vel def 造物(self): pass def 摄像机看向(self, 物体): camera.look_at(物体) camera.rotation_z = 0 def on_ready(self): from ursina import application, window application.time_scale = 1 window_size = (int(1920 / 2), int(1079 / 2)) UniverseSimScenes.set_window_size(window_size, False) window.borderless = False window.position = (20, 20) # 运行前触发 self.sky = create_sphere_sky(texture="bg_pan2.jpg", scale=500000, rotation_x=20, rotation_y=160, rotation_z=20) # UrsinaConfig.trail_type = "line" # UrsinaConfig.trail_length = 91 # UrsinaConfig.trail_type = "line" UrsinaConfig.trail_length = 420 # UrsinaConfig.trail_length = 1000 UrsinaConfig.trail_factor = 3 UrsinaConfig.trail_thickness_factor = 3 camera.clip_plane_near = 0.1 camera.clip_plane_far = 51000000 # camera.position=[1000,1000,-1000] # from sim_scenes.func import create_sphere_sky # create_sphere_sky(scale=500000) # # # 为了较好的立体效果,可以增加太阳光线,光线指向木星(target=jupiter) # create_directional_light(position=(10.5e3, -1e3, -175e3), light_num=1, target=moon, look_at_target_always=True) def 运行(self, 全屏=False): # 使用 ursina 查看的运行效果 # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 # position = 左-右+、上+下-、前+后- bodies = list(self.获取天体列表()) UrsinaEvent.on_ready_subscription(self.on_ready) # UrsinaEvent.on_timer_changed_subscription(on_timer_changed) ursina_run(bodies, SECONDS_PER_DAY, position=(0, 0, -2 * AU), cosmic_bg="", view_closely=0.001) # self.app.run() class 太阳系模拟场景(模拟场景): # def __init__(self): # self.造物() 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) return orbit_line def create_orbit_lines(self): """ 创建太阳系天体的真实轨迹(太阳和哈雷彗星除外) @return: """ self.orbit_lines = [] for body in self.bodies: if isinstance(body, Sun): continue if isinstance(body, Earth): alpha = 0.5 else: alpha = 0.2 orbit_line = self.create_orbit_line(self.太阳, body, self.start_time, alpha=alpha) if orbit_line is not None: self.orbit_lines.append(orbit_line) def set_solar_system_celestial_position(self, bodies, dt, recalc_moon_pos, recalc_moon_pos_scale=50, set_velocity=True, set_acceleration=True): """ 根据日期时间 dt 设置太阳系中天体的真实位置 @param bodies: 太阳系中天体 @param dt: 时间 @param recalc_moon_pos: 是否对月球的位置进行重新计算。 为了更好的展示效果,需要对月球的位置重新计算(使得地月距离放大,月球相对地球方向不变), 重新计算位置后,地球和月球可以放大1000倍以上 @param set_velocity: 是否设置速度 @param set_acceleration: 是否设置加速度 @return: """ earth_pos = None sun_pos = None earth = None sun = None moon = None for body in bodies: if isinstance(body, Sun): sun = body elif isinstance(body, Earth): earth = body elif isinstance(body, Moon): moon = body for body in bodies: if isinstance(body, Asteroids): # 小行星带是模拟,不是正常的天体 posvel = None else: try: # 获取天体的三维位置和矢量速度 posvel = get_body_posvel(body, dt) except Exception: continue if isinstance(body, Moon): # 如果是月球,为了更好的展示效果,需要对月球的位置重新计算 moon_real_pos = [posvel[0].x.value * AU, posvel[0].z.value * AU, posvel[0].y.value * AU] # TODO:注释下行,月球就会在真实的位置 if recalc_moon_pos: posvel = recalc_moon_position(posvel, earth_pos, scale=recalc_moon_pos_scale) if not self.配置项.中心天体 is None: center_pos, _ = get_body_posvel(self.配置项.中心天体.name, dt) center_pos = [center_pos.x.value * AU, center_pos.z.value * AU, center_pos.y.value * AU] else: center_pos = [0, 0, 0] if posvel is None: # posvel 为空,则使用太阳的坐标 position, velocity = [sun_pos.x.value * AU, sun_pos.z.value * AU, sun_pos.y.value * AU], [0, 0, 0] else: # 坐标单位:千米 速度单位:千米/秒 position, velocity = [posvel[0].x.value * AU, posvel[0].z.value * AU, posvel[0].y.value * AU], \ [posvel[1].x.value * AU / SECONDS_PER_DAY, posvel[1].z.value * AU / SECONDS_PER_DAY, posvel[1].y.value * AU / SECONDS_PER_DAY] if not self.配置项.中心天体 is None: position = [position[0]-center_pos[0],position[1]-center_pos[1],position[2]-center_pos[2]] # 实时调整天体的位置和速度 body.position = np.array(position) if set_velocity: body.velocity = np.array(velocity) if isinstance(body, Asteroids): pass elif isinstance(body, Sun): # 记录太阳的位置 sun_pos = posvel[0] elif isinstance(body, Moon): if set_acceleration: # 月球受到2个影响比较大的天体引力(地球和太阳),计算引力引起的加速度和 acc_earth = calc_solar_acceleration(moon_real_pos, earth) acc_sun = calc_solar_acceleration(moon_real_pos, sun) body.acceleration = [acc_earth[0] + acc_sun[0], acc_earth[1] + acc_sun[1], acc_earth[2] + acc_sun[2]] # elif isinstance(body, Earth): # # 月球受到2个影响比较大的天体引力(地球和太阳),计算引力引起的加速度和 # acc_earth = calc_solar_acceleration(earth, moon) # acc_sun = calc_solar_acceleration(earth, sun) # body.acceleration = [acc_earth[0] + acc_sun[0], # acc_earth[1] + acc_sun[1], # acc_earth[2] + acc_sun[2]] else: if set_acceleration: # 其他天体受到太阳引力 body.acceleration = calc_solar_acceleration(body, sun) if isinstance(body, Earth): # 记录地球的位置 earth_pos = posvel[0] def set_bodies_position(self, time_data: TimeData): """ 设置天体的位置(包含速度和加速度的信息) @param time_data: @return: """ t = self.start_time + time_data.total_days self.set_solar_system_celestial_position(self.bodies, t, True, self.配置项.地月距离倍数) def on_ready(self): super(太阳系模拟场景, self).on_ready() if self.配置项.显示轨道: self.create_orbit_lines() def show_clock(self, time_data): """ 显示时钟 @param dt: 时间 datetime @return: """ # if self.clock_position_center: # position, origin = (0, .25), (0, 0), # else: from ursina import window dt = time_data.get_datetime(self.start_time) aspect_ratio = window.aspect_ratio position, origin = (0.5 * aspect_ratio - 0.15, -0.45), (-0.05, 0.1), ControlUI.current_ui.show_message(dt.strftime('%Y-%m-%d'), position=position, origin=origin, # font="verdana.ttf", font="fonts/Digital-7Mono.TTF", font_scale=2, font_color=(0, 255, 0), close_time=-1) def on_timer_changed(self, time_data: TimeData): """ @param time_data: @return: """ dt = time_data.get_datetime(str(self.start_time)) year = dt.strftime("%Y") self.set_bodies_position(time_data) self.show_clock(time_data) def 运行(self, 运行时间=None, 全屏=False): # 使用 ursina 查看的运行效果 # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 # position = 左-右+、上+下-、前+后- if isinstance(运行时间, str): self.start_time = conv_to_astropy_time(运行时间) else: self.start_time = 运行时间 if self.start_time is None: from astropy.time import Time self.start_time = Time.now() self.bodies = list(self.获取天体列表()) UrsinaEvent.on_ready_subscription(self.on_ready) UrsinaEvent.on_timer_changed_subscription(self.on_timer_changed) ursina_run(self.bodies, SECONDS_PER_DAY, position=(0, 0, -2 * AU), cosmic_bg="", gravity_works=False, timer_enabled=True, show_grid=False, show_camera_info=False, view_closely=0.001)