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

Python超人-宇宙模拟器

上级 3d12df6a
...@@ -11,28 +11,30 @@ from common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY, SECONDS_PER_YEAR, A ...@@ -11,28 +11,30 @@ from common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY, SECONDS_PER_YEAR, A
from sim_scenes.func import mayavi_run, ursina_run from sim_scenes.func import mayavi_run, ursina_run
from simulators.ursina.ursina_config import UrsinaConfig from simulators.ursina.ursina_config import UrsinaConfig
from simulators.ursina.ursina_event import UrsinaEvent from simulators.ursina.ursina_event import UrsinaEvent
from ursina import Text, Panel, color, camera
if __name__ == '__main__':
# 八大行星:木星(♃)、土星(♄)、天王星(♅)、海王星(♆)、地球(⊕)、金星(♀)、火星(♂)、水星(☿) def create_bodies():
# 排列顺序 """
# 1、体积:(以地球为1)木星 :土星 :天王星 :海王星 :地球 :金星 :火星 :水星 = 1330:745:65:60:1:0.86:0.15:0.056 创建太阳系天体(忽略质量,引力无效,初速度全部为0)
# 2、质量:(以地球为1)木星 :土星 :天王星 :海王星 :地球 :金星 :火星 :水星 = 318:95:14.53:17.15:1:0.8:0.11:0.0553 太阳、小行星环、
# 3、离太阳从近到远的顺序:水星、金星、地球、火星、木星、土星、天王星、海王星 八大行星:木星(♃)、土星(♄)、天王星(♅)、海王星(♆)、地球(⊕)、金星(♀)、火星(♂)、水星(☿)
# ===================================================================== 冥王星
# 以下展示的效果为太阳系真实的距离 以下展示的效果为太阳系真实的距离
# 由于宇宙空间尺度非常大,如果按照实际的天体大小,则无法看到天体,因此需要对天体的尺寸进行放大 @return:
sun = Sun(name="太阳", size_scale=0.8e2) # 太阳放大 80 倍,距离保持不变 """
sun = Sun(name="太阳", size_scale=0.6e2) # 太阳放大 60 倍,距离保持不变
bodies = [ bodies = [
sun, sun,
Mercury(name="水星", size_scale=4e3), # 水星放大 4000 倍,距离保持不变 Mercury(name="水星", size_scale=3e3), # 水星放大 3000 倍,距离保持不变
Venus(name="金星", size_scale=4e3), # 金星放大 4000 倍,距离保持不变 Venus(name="金星", size_scale=4e3), # 金星放大 4000 倍,距离保持不变
Earth(name="地球", size_scale=4e3), # 地球放大 4000 倍,距离保持不变 Earth(name="地球", size_scale=4e3), # 地球放大 4000 倍,距离保持不变
Mars(name="火星", size_scale=4e3), # 火星放大 4000 倍,距离保持不变 Mars(name="火星", size_scale=4e3), # 火星放大 4000 倍,距离保持不变
Asteroids(name="小行星群", size_scale=3.2e2, Asteroids(name="小行星群", size_scale=3.2e2,
parent=sun), # 小行星群模拟(仅 ursina 模拟器支持) parent=sun), # 小行星群模拟(仅 ursina 模拟器支持)
Jupiter(name="木星", size_scale=0.8e3), # 木星放大 800 倍,距离保持不变 Jupiter(name="木星", size_scale=0.7e3), # 木星放大 600 倍,距离保持不变
Saturn(name="土星", size_scale=0.8e3), # 土星放大 800 倍,距离保持不变 Saturn(name="土星", size_scale=0.7e3), # 土星放大 600 倍,距离保持不变
Uranus(name="天王星", size_scale=0.8e3), # 天王星放大 800 倍,距离保持不变 Uranus(name="天王星", size_scale=0.7e3), # 天王星放大 600 倍,距离保持不变
Neptune(name="海王星", size_scale=1e3), # 海王星放大 1000 倍,距离保持不变 Neptune(name="海王星", size_scale=1e3), # 海王星放大 1000 倍,距离保持不变
Pluto(name="冥王星", size_scale=10e3), # 冥王星放大 10000 倍,距离保持不变(从太阳系的行星中排除) Pluto(name="冥王星", size_scale=10e3), # 冥王星放大 10000 倍,距离保持不变(从太阳系的行星中排除)
] ]
...@@ -41,41 +43,98 @@ if __name__ == '__main__': ...@@ -41,41 +43,98 @@ if __name__ == '__main__':
for idx, body in enumerate(bodies): for idx, body in enumerate(bodies):
body.set_ignore_mass(True) # 忽略质量(引力无效) body.set_ignore_mass(True) # 忽略质量(引力无效)
body.init_velocity = [0, 0, 0] # 初速度为0 body.init_velocity = [0, 0, 0] # 初速度为0
return bodies
# 用天体模拟一个光子 def create_light():
light_body = Body(name='光', mass=0, size_scale=1e4, color=(255, 255, 0), """
用天体模拟一个光子
@return:
"""
return Body(name='光速', mass=0, size_scale=1e4, color=(255, 255, 0),
init_position=[AU / 2, 0, 0], init_position=[AU / 2, 0, 0],
init_velocity=[0, 0, 299792.458]).set_light_disable(True) # 1 光速=299792.458 千米/秒(km/秒) init_velocity=[0, 0, 299792.458]).set_light_disable(True) # 1光速=299792.458 千米/秒(km/秒)
def create_text_panel(width=0.35, height=.5):
# 创建一个 Panel 组件
panel = Panel(
parent=None,
model='quad',
# texture='white_cube',
color=color.gray,
origin=(-.48, .48),
scale=(width, height),
position=(-.88, 0.3, 0),
alpha=0.2
)
bodies.append(light_body) # 创建一个 Text 组件用于显示消息
text = Text(
parent=panel,
text='',
origin=(-.5, .5),
scale=(height * 5, width * 5),
font=UrsinaConfig.CN_FONT,
# background=True,
# background_color=color.clear
)
return text
arrived_bodies = []
def on_reset(): # 已到达天体列表
arrived_bodies = []
text_panel = None
arrived_info = ""
def on_reset():
global arrived_info
arrived_bodies.clear() arrived_bodies.clear()
arrived_info = ""
if text_panel is not None:
text_panel.text = ""
def on_timer_changed(time_text, time_data):
years, days, hours, minutes, seconds = time_data
# UrsinaEvent.on_timer_changed(self.text, (years, days, hours, minutes, seconds))
# 光到达地球8.3分钟,
# 光到达冥王星平均用时要20000秒,333.3分钟 也就是约5.56小时
def on_ready():
global text_panel
text_panel = create_text_panel()
def on_timer_changed(time_text, time_data):
global arrived_info
years, days, hours, minutes, seconds = time_data
for body in bodies: for body in bodies:
if body is light_body or isinstance(body, Sun) \ if body is light_body or isinstance(body, Sun) \
or body in arrived_bodies or isinstance(body, Asteroids): or body in arrived_bodies or isinstance(body, Asteroids):
# 对于光速天体、太阳、小行星群、“已到达天体列表”中的天体无需计算
continue continue
# 计算判断,如果光速天体距离到达了某个天体,就记录到“已到达天体列表”中
if light_body.position[2] >= body.position[2]: if light_body.position[2] >= body.position[2]:
arrived_bodies.append(body) arrived_bodies.append(body)
print(f"[{time_text}] 已经到达 [{body.name}]") if text_panel is not None:
# print(light_body.planet) arrived_info += f"[{time_text}]\t到达\t[{body.name}]\n\n"
text_panel.text = arrived_info
print(f"[{time_text}] 到达 [{body.name}]")
# 订阅计时器事件(记录已到达天体列表)
UrsinaEvent.on_timer_changed_subscription(on_timer_changed)
# 订阅重新开始事件
UrsinaEvent.on_reset_subscription(on_reset)
UrsinaEvent.on_ready_subscription(on_ready)
# 创建太阳系天体(忽略质量,引力无效,初速度全部为0)
bodies = create_bodies()
# 创建一个以光速前进的天体(模拟一个光子,质量为0才能达到光速)
light_body = create_light()
UrsinaEvent.on_timer_changed_subscription(on_timer_changed) bodies.append(light_body)
UrsinaEvent.on_reset_subscription(on_reset)
# 使用 ursina 查看的运行效果 # 使用 ursina 查看的运行效果
# 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹
# position = 左-右+、上+下-、前+后- # position = 左-右+、上+下-、前+后-
ursina_run(bodies, 60, position=(0, 2 * AU, -11 * AU), ursina_run(bodies, 60, position=(0, 2 * AU, -11 * AU),
show_trail=True, show_timer=True, show_trail=True, show_timer=True,
bg_music="sounds/interstellar.mp3") bg_music="sounds/interstellar.mp3")
...@@ -39,8 +39,8 @@ class Timer(Text): ...@@ -39,8 +39,8 @@ class Timer(Text):
# self.text = f'{minutes:02d}:{seconds:02d}' # self.text = f'{minutes:02d}:{seconds:02d}'
time_scale = UrsinaConfig.get_app_time_scale() time_scale = UrsinaConfig.get_app_time_scale()
current_time = datetime.datetime.now() current_time = datetime.datetime.now()
# 0.653 是对测试太阳系时间的纠正 # 0.6 是对测试太阳系时间的纠正
self.elapsed_time += (current_time - self.last_time) * evolve_dt * time_scale * 0.653 self.elapsed_time += (current_time - self.last_time) * evolve_dt * time_scale * 0.6
# datetime.timedelta(microseconds=1) 0:00:00.000001 # datetime.timedelta(microseconds=1) 0:00:00.000001
# datetime.timedelta(milliseconds=1) 0:00:00.001000 # datetime.timedelta(milliseconds=1) 0:00:00.001000
# self.elapsed_time += self.elapsed_time_offset # 按区域取值 # self.elapsed_time += self.elapsed_time_offset # 按区域取值
......
...@@ -24,6 +24,8 @@ class UrsinaEvent: ...@@ -24,6 +24,8 @@ class UrsinaEvent:
UrsinaEvent.on_pause_funcs = [] UrsinaEvent.on_pause_funcs = []
# 启动运行的订阅事件 # 启动运行的订阅事件
UrsinaEvent.on_start_funcs = [] UrsinaEvent.on_start_funcs = []
# 运行准备的订阅事件
UrsinaEvent.on_ready_funcs = []
# 搜索天体的订阅事件 # 搜索天体的订阅事件
UrsinaEvent.on_searching_bodies_funcs = [] UrsinaEvent.on_searching_bodies_funcs = []
...@@ -80,6 +82,15 @@ class UrsinaEvent: ...@@ -80,6 +82,15 @@ class UrsinaEvent:
for f in UrsinaEvent.on_reset_funcs: for f in UrsinaEvent.on_reset_funcs:
f() f()
@staticmethod
def on_ready_subscription(fun):
UrsinaEvent.on_ready_funcs.append(fun)
@staticmethod
def on_ready():
for f in UrsinaEvent.on_ready_funcs:
f()
@staticmethod @staticmethod
def on_start_subscription(fun): def on_start_subscription(fun):
UrsinaEvent.on_start_funcs.append(fun) UrsinaEvent.on_start_funcs.append(fun)
......
...@@ -242,7 +242,8 @@ class UrsinaSimulator(Simulator): ...@@ -242,7 +242,8 @@ class UrsinaSimulator(Simulator):
camera.fov = 60 camera.fov = 60
window.fps_counter.enabled = False window.fps_counter.enabled = False
window.editor_ui.enabled = False # window.exit_button.enabled = False
# window.editor_ui.enabled = True
# # 场景加入雾的效果 # # 场景加入雾的效果
# scene.fog_color = color.orange # scene.fog_color = color.orange
...@@ -275,8 +276,6 @@ class UrsinaSimulator(Simulator): ...@@ -275,8 +276,6 @@ class UrsinaSimulator(Simulator):
if cosmic_bg is not None and os.path.exists(cosmic_bg): if cosmic_bg is not None and os.path.exists(cosmic_bg):
self.cosmic_background(cosmic_bg) self.cosmic_background(cosmic_bg)
# ui = UrsinaUI() # ui = UrsinaUI()
ctl = ControlUI(ControlHandler(), position=(0.6, 0.5)) ctl = ControlUI(ControlHandler(), position=(0.6, 0.5))
if show_timer: if show_timer:
...@@ -309,6 +308,11 @@ class UrsinaSimulator(Simulator): ...@@ -309,6 +308,11 @@ class UrsinaSimulator(Simulator):
audio = Audio(bg_music, pitch=1, loop=True, autoplay=True) audio = Audio(bg_music, pitch=1, loop=True, autoplay=True)
audio.volume = 0.3 audio.volume = 0.3
if show_timer:
UrsinaEvent.on_reset()
UrsinaEvent.on_ready()
self.app.run() self.app.run()
......
# -*- coding:utf-8 -*-
# title :天体视图
# description :天体视图(天体效果展示用)
# author :Python超人
# date :2023-02-11
# link :https://gitcode.net/pythoncr/
# python_version :3.8
# ==============================================================================
from abc import ABCMeta, abstractmethod
from bodies import Body
from common.func import get_dominant_colors
import numpy as np
import os
class BodyView(metaclass=ABCMeta):
"""
天体视图(天体效果展示用)
"""
def __init__(self, body: Body):
self.body = body
self.sphere = None
if self.body.texture is None or self.body.texture == '':
self.color = tuple(np.array(body.color) / 255)
else:
self.texture = self.__find_texture(self.body.texture) # 纹理
if self.texture is None:
self.color = tuple(np.array(body.color) / 255)
else:
self.color = self.__get_texture_main_color(self.texture)
self.appear()
self.position = [None, None, None]
self.name = None
self.mass = None
self.raduis = None
self.velocity = None
self.angle = 0
self.appeared = True
def __repr__(self):
return '<%s> m=%.3e(kg), r=%.3e(km), p=[%.3e,%.3e,%.3e](km), v=%s(km/s)' % \
(self.name, self.mass, self.raduis,
self.position[0], self.position[1], self.position[2], self.velocity)
def __find_texture(self, texture):
"""
尝试在多个路径下寻找纹理图片
:param texture: 纹理图片
:return: 纹理图片的路径
"""
paths = ['./textures', '../textures']
for path in paths:
p = path + "/" + texture
if os.path.exists(p):
return p
return None
def __get_texture_main_color(self, texture):
"""
获取纹理图片的主要颜色
:param texture:
:return:
"""
colors = get_dominant_colors(texture)
first_color = colors[0]
# print(self.name, first_color)
return tuple(np.array(first_color) / 255)
@abstractmethod
def update(self):
"""
更新天体信息和数据,比如:更新天体的位置
:return:
"""
pass
def disappear(self):
"""
天体消失的操作,比如:销毁天体视图对象
:return:
"""
pass
@abstractmethod
def appear(self):
"""
天体显示的操作,比如:构建天体视图对象
:return:
"""
pass
# -*- coding:utf-8 -*-
# title :Mayavi天体视图
# description :Mayavi天体视图(天体效果展示用,需要安装 mayavi)
# author :Python超人
# date :2023-02-11
# link :https://gitcode.net/pythoncr/
# python_version :3.8
# ==============================================================================
from mayavi import mlab
from tvtk.api import tvtk
import os
import matplotlib.pyplot as plt
from common.func import get_dominant_colors
from simulators.views.body_view import BodyView
import numpy as np
class MayaviView(BodyView):
"""
Mayavi天体视图(天体效果展示用)
"""
def update(self):
"""
更新天体信息和数据,比如:更新天体的位置
:return:
"""
if hasattr(self.sphere, "mlab_source"):
# self.sphere.mlab_source.x 的位置是已经和 distance_scale 进行了相乘
# body.position 是真实位置,所以需要和 distance_scale 相乘
x_offset = self.body.position[0] * self.body.distance_scale - self.sphere.mlab_source.x
y_offset = self.body.position[1] * self.body.distance_scale - self.sphere.mlab_source.y
z_offset = self.body.position[2] * self.body.distance_scale - self.sphere.mlab_source.z
# self.position 的位置是已经和 distance_scale 进行了相乘
self.sphere.mlab_source.set(x=self.position[0], y=self.position[1], z=self.position[2])
# self.angle += 10
# self.sphere.actor.actor.rotate_z(self.angle)
# self.sphere.actor.actor.rotate_wxyz(self.angle,0,0,0)
if hasattr(self, "rings"):
if hasattr(self.rings, "mlab_source"):
if hasattr(self, "rings") and self.body.has_rings:
x = self.rings.mlab_source.x
y = self.rings.mlab_source.y
z = self.rings.mlab_source.z
x = x + x_offset[0]
y = y + y_offset[0]
z = z + z_offset[0]
self.rings.mlab_source.set(x=x, y=y, z=z)
# return x_offset[0], y_offset[0], z_offset[0]
def build_rings(self):
if not hasattr(self, "rings") or self.rings is None:
R = 2.2
r = 0.5
rings_scale = 0.5e5
resolution = 50
theta = np.linspace(0, 2 * np.pi, resolution)
phi = np.linspace(0, 2 * np.pi, resolution)
torus = np.zeros((3, resolution, resolution))
# # body.position 是真实位置,所以需要和 distance_scale 相乘
for i in range(0, resolution):
for j in range(0, resolution): # size_scale=8.0e2
torus[0][i][j] = (R + r * np.cos(phi[j])) * np.cos(theta[i]) * \
self.body.size_scale * rings_scale + \
self.body.position[0] # * self.body.distance_scale
torus[1][i][j] = (R + r * np.cos(phi[j])) * np.sin(theta[i]) * \
self.body.size_scale * rings_scale + \
self.body.position[1] # * self.body.distance_scale
# 带环的厚度
thicknesses_scale = self.body.raduis * 20
torus[2][i][j] = thicknesses_scale * np.sin(phi[j]) + \
self.body.position[2] # * self.body.distance_scale
rings_color = (173 / 255, 121 / 255, 92 / 255)
if hasattr(self.body, "rings_color"):
rings_color = tuple(np.array(self.body.rings_color) / 255)
self.rings = mlab.mesh(torus[0], torus[1], torus[2], color=rings_color,
representation='surface')
return self.rings
def appear(self):
"""
天体显示的操作,比如:构建天体视图对象
:return:
"""
if not hasattr(self, "sphere") or self.sphere is None:
scale_factor = self.body.size_scale * self.body.diameter
sphere = mlab.points3d(self.body.position[0], self.body.position[1], self.body.position[2],
scale_factor=scale_factor,
color=self.color,
resolution=50,
opacity=1,
name=self.body.name)
# # 调整镜面反射参数
sphere.actor.property.specular = 0.5 # 0.1
sphere.actor.property.specular_power = 128
# 设置背面剔除,以更好的显示透明效果
sphere.actor.property.backface_culling = True
# sphere.actor.property.lighting_ = 10
sphere.actor.property.lighting = False
sphere.scene.disable_render = False
# sphere_earth.scene.disable_render = False
sphere.scene.anti_aliasing_frames = 10
# sphere_earth.scene.anti_aliasing_frames = 0
self.sphere = sphere
if hasattr(self, "texture"):
if self.texture is not None and self.texture != '':
self.__set_texture(self.texture)
if self.body.has_rings:
self.build_rings()
# return self.sphere, self.rings
# return self.sphere,
def disappear(self):
if hasattr(self, "sphere"):
self.sphere.visible = False
if hasattr(self, "rings"):
self.rings.visible = False
def __set_texture(self, image_file):
"""
设置纹理图片到天体
:param image_file:
:return:
"""
outfile = image_file.replace('.jpg', '_flipped.jpg')
if os.path.exists(outfile):
image_file = outfile
else:
if not os.path.exists(image_file):
return
img = plt.imread(image_file)
img = img[::-1, ...]
plt.imsave(outfile, img)
image_file = outfile
img = tvtk.JPEGReader(file_name=image_file)
texture = tvtk.Texture(input_connection=img.output_port, interpolate=0, repeat=0)
self.sphere.actor.actor.texture = texture
self.sphere.actor.tcoord_generator_mode = 'sphere'
cylinder_mapper = self.sphere.actor.tcoord_generator
cylinder_mapper.prevent_seam = 0
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册