diff --git a/objs/__init__.py b/objs/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/objs/obj.py b/objs/obj.py new file mode 100644 index 0000000000000000000000000000000000000000..d1cdb53481ad5fbc425b3852af42c315106a2372 --- /dev/null +++ b/objs/obj.py @@ -0,0 +1,427 @@ +# -*- 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) diff --git a/sim_scenes/func.py b/sim_scenes/func.py index b0dbd23734bd9550878d18f35f23fc11d1393613..56a5d28c07fd02a928e283aeb0128a71441359ce 100644 --- a/sim_scenes/func.py +++ b/sim_scenes/func.py @@ -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 倍,距离保持不变 diff --git a/sim_scenes/funny/dancing_with_jupiter.py b/sim_scenes/funny/dancing_with_jupiter.py index c115d3907151b1607aeb231640d428bd7074e71b..03b77d3dff0a112c2303823eb9e1e61685add2e6 100644 --- a/sim_scenes/funny/dancing_with_jupiter.py +++ b/sim_scenes/funny/dancing_with_jupiter.py @@ -21,8 +21,8 @@ if __name__ == '__main__': """ # 选择舞者 Dancer = Earth # 舞者为地球 - Dancer = Venus # 舞者为金星 - Dancer = Mars # 舞者为火星 + # Dancer = Venus # 舞者为金星 + # Dancer = Mars # 舞者为火星 bodies = [ Sun(size_scale=0.8e2), # 太阳放大 80 倍 diff --git a/sim_scenes/solar_system/speed_of_light.py b/sim_scenes/solar_system/speed_of_light.py index 32c4afe1164e0846d2abe83b1995212cc292e390..b3f2864e5649b440d08e89625a9daee9ec53a61b 100644 --- a/sim_scenes/solar_system/speed_of_light.py +++ b/sim_scenes/solar_system/speed_of_light.py @@ -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) diff --git a/sim_scenes/solar_system/speed_of_light_init.py b/sim_scenes/solar_system/speed_of_light_init.py index 02d13f082b97f0e3b4b2bb784b31b13d5caa4c66..dbbf5de038ae153feeb4df91433fbc745eba69b9 100644 --- a/sim_scenes/solar_system/speed_of_light_init.py +++ b/sim_scenes/solar_system/speed_of_light_init.py @@ -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) diff --git a/sim_scenes/solar_system/transit_of_venus_mercury.py b/sim_scenes/solar_system/transit_of_venus_mercury.py index 68ab862ead82324c5e1df1fde5b138435ba1a53f..56521e01631520a58d9791436c52aeec1923b774 100644 --- a/sim_scenes/solar_system/transit_of_venus_mercury.py +++ b/sim_scenes/solar_system/transit_of_venus_mercury.py @@ -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) diff --git a/simulators/ursina_simulator.py b/simulators/ursina_simulator.py index 8ce7fc7c30360bcfb0ca003716cf0f5685f11fd1..1d8cc4f7151d94a5ed6a0e186d8c20e853954987 100644 --- a/simulators/ursina_simulator.py +++ b/simulators/ursina_simulator.py @@ -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: diff --git a/textures/transparent.png b/textures/transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..3b64fe1804f79e00cdc691412b427e78d6f1c10b Binary files /dev/null and b/textures/transparent.png differ