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

Python超人-宇宙模拟器

上级 e3fc23af
# -*- 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
import json
import numpy as np
import math
from common.consts import AU
import copy
class Obj(metaclass=ABCMeta):
"""
对象基类
"""
def __init__(self, name, mass, init_position, init_velocity,
density=5e3, color=(125 / 255, 125 / 255, 125 / 255),
texture=None, size_scale=1.0, distance_scale=1.0,
parent=None, ignore_mass=False,
trail_color=None, show_name=False):
"""
对象类
@param name: 对象名称
@param mass: 对象质量 (kg)
@param init_position: 初始位置 (km)
@param init_velocity: 初始速度 (km/s)
@param density: 平均密度 (kg/m³)
@param color: 对象颜色(纹理图片优先)
@param texture: 纹理图片
@param size_scale: 尺寸缩放
@param distance_scale: 距离缩放
@param parent: 对象的父对象
@param ignore_mass: 是否忽略质量(如果为True,则不计算引力)
TODO: 注意:这里的算法是基于牛顿的万有引力(质量为0不受引力的影响在对象物理学中是不严谨)
@param trail_color: 对象拖尾颜色(默认对象颜色)
@param show_name: 是否显示对象名称
"""
self.__his_pos = []
self.__his_vel = []
self.__his_acc = []
self.__his_reserved_num = 200
if name is None:
name = getattr(self.__class__, '__name__')
self.name = name
self.__mass = mass
# 是否忽略质量(如果为True,则不计算引力)
# TODO: 注意:这里的算法是基于牛顿的万有引力(质量为0不受引力的影响在对象物理学中是不严谨)
if self.__mass <= 0: # 质量小于等于0就忽略
self.ignore_mass = True
else:
self.ignore_mass = ignore_mass
self.__init_position = None
self.__init_velocity = None
self.init_position = np.array(init_position, dtype='float32')
self.init_velocity = np.array(init_velocity, dtype='float32')
self.__density = density
self.color = color
self.trail_color = color if trail_color is None else trail_color
self.texture = texture
self.size_scale = size_scale
self.distance_scale = distance_scale
# 初始化后,加速度为0,只有多个对象的引力才会影响到加速度
# km/s²
self.__acceleration = np.array([0, 0, 0], dtype='float32')
self.__record_history()
# 是否显示
self.appeared = True
self.parent = parent
self.show_name = show_name
self.resolution = None
self.light_disable = False
self.__has_rings = False
def set_ignore_gravity(self, value=True):
"""
设置忽略质量,True为引力失效
@param value:
@return:
"""
# TODO: 注意:这里的算法是基于牛顿的万有引力(质量为0不受引力的影响在对象物理学中是不严谨)
if self.__mass <= 0: # 质量小于等于0就忽略
self.ignore_mass = True
else:
self.ignore_mass = value
return self
def set_light_disable(self, value=True):
"""
设置灯光为无效
@param value:
@return:
"""
self.light_disable = value
return self
def set_resolution(self, value):
"""
设置对象的分辨率
@param value:
@return:
"""
self.resolution = value
return self
@property
def init_position(self):
"""
获取对象的初始位置(单位:km)
@return:
"""
return self.__init_position
@init_position.setter
def init_position(self, value):
"""
设置对象的初始位置(单位:km)
@param value:
@return:
"""
self.__init_position = np.array(value, dtype='float32')
self.__position = copy.deepcopy(self.__init_position)
@property
def init_velocity(self):
"""
获取对象的初始速度 (km/s)
@return:
"""
return self.__init_velocity
@init_velocity.setter
def init_velocity(self, value):
"""
设置对象的初始速度 (km/s)
@param value:
@return:
"""
self.__init_velocity = np.array(value, dtype='float32')
self.__velocity = copy.deepcopy(self.__init_velocity)
@property
def position(self):
"""
获取对象的位置(单位:km)
@return:
"""
return self.__position
@position.setter
def position(self, value):
"""
设置对象的位置(单位:km)
@param value:
@return:
"""
self.__position = value
self.__record_history()
@property
def acceleration(self):
"""
获取对象的加速度(单位:km/s²)
@return:
"""
return self.__acceleration
@acceleration.setter
def acceleration(self, value):
"""
设置对象的加速度(单位:km/s²)
@param value:
@return:
"""
self.__acceleration = np.array(value, dtype=float)
self.__record_history()
def stop(self):
"""
停止运动,将加速度和速度置零
@return:
"""
self.init_velocity = [0.0, 0.0, 0.0]
self.acceleration = [0.0, 0.0, 0.0]
def stop_and_ignore_gravity(self):
"""
停止运动,并忽略质量(不受引力影响)
TODO: 注意:这里的算法是基于牛顿的万有引力(质量为0不受引力的影响在对象物理学中是不严谨)
@return:
"""
self.set_ignore_gravity()
self.stop()
@property
def velocity(self):
"""
获取对象的速度(单位:km/s)
@return:
"""
return self.__velocity
@velocity.setter
def velocity(self, value):
"""
设置对象的速度(单位:km/s)
@param value:
@return:
"""
self.__velocity = value
self.__record_history()
def __append_history(self, his_list, data):
"""
追加每个位置时刻的历史数据
@param his_list:
@param data:
@return:
"""
# 如果历史记录为0 或者 新增数据和最后的历史数据不相同,则添加
if len(his_list) == 0 or \
np.sum(data == his_list[-1]) < len(data):
his_list.append(data.copy())
def __record_history(self):
"""
记录每个位置时刻的历史数据
@return:
"""
# 如果历史记录数超过了保留数量,则截断,只保留 __his_reserved_num 数量的历史
if len(self.__his_pos) > self.__his_reserved_num:
self.__his_pos = self.__his_pos[len(self.__his_pos) - self.__his_reserved_num:]
self.__his_vel = self.__his_vel[len(self.__his_vel) - self.__his_reserved_num:]
self.__his_acc = self.__his_acc[len(self.__his_acc) - self.__his_reserved_num:]
# 追加历史记录(位置、速度、加速度)
self.__append_history(self.__his_pos, self.position)
self.__append_history(self.__his_vel, self.velocity)
self.__append_history(self.__his_acc, self.acceleration)
# print(self.name, "his pos->", self.__his_pos)
def his_position(self):
"""
历史位置
@return:
"""
return self.__his_pos
def his_velocity(self):
"""
历史瞬时速度
@return:
"""
return self.__his_vel
def his_acceleration(self):
"""
历史瞬时加速度
@return:
"""
return self.__his_acc
@property
def mass(self):
"""
对象质量 (单位:kg)
@return:
"""
return self.__mass
@property
def density(self):
"""
平均密度 (单位:kg/m³)
@return:
"""
return self.__density
def __repr__(self):
return '<%s(%s)> m=%.3e(kg), d=%.3e(kg/m³), p=[%.3e,%.3e,%.3e](km), v=%s(km/s)' % \
(self.name, self.__class__.__name__, self.mass, self.density,
self.position[0], self.position[1], self.position[2], self.velocity)
def ignore_gravity_with(self, body):
"""
是否忽略指定对象的引力
@param body:
@return:
"""
# TODO: 注意:这里的算法是基于牛顿的万有引力(质量为0不受引力的影响在对象物理学中是不严谨)
if self.ignore_mass:
return True
return False
def position_au(self):
"""
获取对象的位置(单位:天文单位 A.U.)
@return:
"""
pos = self.position
pos_au = pos / AU
return pos_au
# def change_velocity(self, dv):
# self.velocity += dv
#
# def move(self, dt):
# self.position += self.velocity * dt
def reset(self):
"""
重新设置初始速度和初始位置
@return:
"""
self.position = copy.deepcopy(self.init_position)
self.velocity = copy.deepcopy(self.init_velocity)
# def kinetic_energy(self):
# """
# 计算动能(千焦耳)
# 表示动能,单位为焦耳j,m为质量,单位为千克,v为速度,单位为米/秒。
# ek=(1/2).m.v^2
# m(kg) v(m/s) -> j
# m(kg) v(km/s) -> kj
# """
# v = self.velocity
# return 0.5 * self.mass * (v[0] ** 2 + v[1] ** 2 + v[2] ** 2)
@staticmethod
def build_objs_from_json(json_file):
"""
JSON文件转为对象对象
@param json_file:
@return:
"""
bodies = []
params = {}
from bodies import FixedStar, Body
with open(json_file, "r", encoding='utf-8') as read_content:
json_data = json.load(read_content)
for body_data in json_data["bodies"]:
try:
body_data = Obj.exp(body_data) # print(body_data)
except Exception as e:
err_msg = f"{json_file} 格式错误:" + str(e)
raise Exception(err_msg)
is_fixed_star = False
if "is_fixed_star" in body_data:
if body_data["is_fixed_star"]:
is_fixed_star = True
if is_fixed_star:
body_data.pop("is_fixed_star")
body = FixedStar(**body_data)
else:
has_rings = False
if "has_rings" in body_data:
if body_data["has_rings"]:
has_rings = True
body_data.pop("has_rings")
if "rotation_speed" in body_data:
body = Body(**body_data)
if has_rings:
body.has_rings = True
else:
body_data.pop("rotation_speed")
body_data.pop("is_fixed_star")
body = Obj(**body_data)
# [x, y, z]->[-y, z, x]
# body.init_velocity = [-body.init_velocity[1],body.init_velocity[2],body.init_velocity[0]]
# body.init_position = [-body.init_position[1],body.init_position[2],body.init_position[0]]
bodies.append(body)
if "params" in json_data:
params = json_data["params"]
# print(body.position_au())
return bodies, params
@staticmethod
def exp(body_data):
"""
进行表达式分析,将表达式改为eval执行后的结果
@param body_data:
@return:
"""
#
for k in body_data.keys():
v = body_data[k]
if isinstance(v, str):
if v.startswith("$exp:"):
exp = v[5:]
body_data[k] = eval(exp)
elif isinstance(v, list):
for idx, item in enumerate(v):
if isinstance(item, str):
if item.startswith("$exp:"):
exp = item[5:]
v[idx] = eval(exp)
return body_data
if __name__ == '__main__':
# build_bodies_from_json('../data/sun.json')
objs, params = Obj.build_objs_from_json('../data/sun_earth.json')
for obj in objs:
print(obj)
......@@ -190,10 +190,10 @@ def create_solar_system_bodies(ignore_mass=False, init_velocity=None):
sun = Sun(name="太阳", size_scale=0.5e2) # 太阳放大 50 倍,距离保持不变
bodies = [
sun,
Mercury(name="水星", size_scale=1e3), # 水星放大 1000 倍,距离保持不变
Venus(name="金星", size_scale=1e3), # 金星放大 1000 倍,距离保持不变
Earth(name="地球", size_scale=1e3), # 地球放大 1000 倍,距离保持不变
Mars(name="火星", size_scale=1e3), # 火星放大 1000 倍,距离保持不变
Mercury(name="水星", size_scale=0.3e3), # 水星放大 300 倍,距离保持不变
Venus(name="金星", size_scale=0.3e3), # 金星放大 300 倍,距离保持不变
Earth(name="地球", size_scale=0.3e3), # 地球放大 300 倍,距离保持不变
Mars(name="火星", size_scale=0.3e3), # 火星放大 300 倍,距离保持不变
# Asteroids(name="小行星群", size_scale=3.2e2,
# parent=sun), # 小行星群模拟(仅 ursina 模拟器支持)
Jupiter(name="木星", size_scale=0.3e3), # 木星放大 300 倍,距离保持不变
......
......@@ -21,8 +21,8 @@ if __name__ == '__main__':
"""
# 选择舞者
Dancer = Earth # 舞者为地球
Dancer = Venus # 舞者为金星
Dancer = Mars # 舞者为火星
# Dancer = Venus # 舞者为金星
# Dancer = Mars # 舞者为火星
bodies = [
Sun(size_scale=0.8e2), # 太阳放大 80 倍
......
......@@ -12,9 +12,9 @@ from sim_scenes.solar_system.speed_of_light_init import SpeedOfLightInit
# TODO: 三种不同的摄像机视角
camera_follow_light = None # 摄像机固定,不会跟随光
# camera_follow_light = 'ForwardView' # 摄像机跟随光,方向是向前看
# camera_follow_light = 'SideView' # 摄像机跟随光,方向是从侧面看
# camera_follow_light = 'SideViewActualSize' # 摄像机跟随光,方向是从侧面看,天体是实际大小
camera_follow_light = 'ForwardView' # 摄像机跟随光,方向是向前看
camera_follow_light = 'SideView' # 摄像机跟随光,方向是从侧面看
camera_follow_light = 'SideViewActualSize' # 摄像机跟随光,方向是从侧面看,天体是实际大小
# 实例化一个初始化对象(订阅事件,记录到达每个行星所需要的时间)
init = SpeedOfLightInit(camera_follow_light)
......
......@@ -85,7 +85,7 @@ class SpeedOfLightInit:
@return:
"""
self.arrived_bodies.clear() # 重置存放记录光体已到达天体列表
self.arrived_info = "距离[太阳]:${distance}\n\n"
self.arrived_info = "距离[太阳中心]:${distance}\n\n"
if self.text_panel is not None:
self.text_panel.text = self.arrived_info.replace("${distance}", "0 AU")
......@@ -141,6 +141,11 @@ class SpeedOfLightInit:
camera.parent = self.light_body.planet
self.light_body.planet.input = self.light_body_input
camera.rotation_y = -15
if hasattr(camera, "sky"):
# 摄像机跟随地球后,需要对深空背景进行调整,否则看到的是黑色背景
camera.sky.scale = 800
camera.clip_plane_near = 0.1
camera.clip_plane_far = 1000000
# 取消订阅(防止 光体 的大小进行变化影响摄像机的视角)
UrsinaEvent.on_body_size_changed_unsubscription(self.light_body.planet.change_body_scale)
......
......@@ -14,30 +14,49 @@ from simulators.ursina.ursina_event import UrsinaEvent
if __name__ == '__main__':
# 水星、金星凌日
earth = Earth(name="地球")
sun = Sun(name="太阳", size_scale=5e1) # 太阳放大 20 倍
earth = Earth(name="地球", rotation_speed=0, texture="transparent.png") # 地球纹理透明,不会挡住摄像机视线
sun = Sun(name="太阳", size_scale=5e1) # 太阳放大 50 倍
bodies = [
sun,
earth,
Mercury(name="水星",
init_position=[0.384 * AU, 0, 0],
init_velocity=[0, 0, 47.87],
size_scale=5e1), # 水星放大 10 倍,距离保持不变
size_scale=5e1), # 水星放大 50 倍,距离保持不变
Venus(name="金星",
init_position=[0.721 * AU, 0, 0],
init_velocity=[0, 0, 35],
size_scale=5e1) # 金星放大 10 倍,距离保持不变
size_scale=5e1) # 金星放大 50 倍,距离保持不变
]
def on_ready():
camera_look_at(sun, rotation_x=None, rotation_y=None, rotation_z=0)
pass
from ursina import camera
# 摄像机跟随地球(模拟在地球上看到的效果)
camera.parent = earth.planet
if hasattr(camera, "sky"):
# 摄像机跟随地球后,需要对深空背景进行调整,否则看到的是黑色背景
camera.sky.scale = 800
camera.clip_plane_near = 0.1
camera.clip_plane_far = 1000000
# 让太阳的旋转速度放慢10倍
sun.rotation_speed /= 10
def on_timer_changed(time_data: TimeData):
# 时时刻刻的让地球看向太阳(摄像机跟随地球看向太阳)
earth.planet.look_at(sun.planet)
earth.planet.rotation_z = 0
UrsinaEvent.on_ready_subscription(on_ready)
UrsinaEvent.on_timer_changed_subscription(on_timer_changed)
# 使用 ursina 查看的运行效果
# 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹
# position = 左-右+、上+下-、前+后-
ursina_run(bodies, SECONDS_PER_DAY * 3,
position=earth.init_position)
ursina_run(bodies, SECONDS_PER_WEEK,
position=[0, 0, 0], # 以地球为中心的位置
show_timer=True)
......@@ -169,6 +169,7 @@ class UrsinaSimulator(Simulator):
# sky = SphereSky(texture=texture, scale=sky_scale)
sky.scale = sky_scale
camera.sky = sky
# sky.set_shader_input('texture_scale', Vec2(20, 20))
# 一定要够大,如果小于 Sky(texture=texture).scale = 50000,宇宙背景就会出现黑色方洞
if camera.clip_plane_far < sky_scale * 2:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册