# -*- coding:utf-8 -*- # title :虫洞效果模拟 # description :虫洞效果模拟 # author :Python超人 # date :2023-11-19 # link :https://gitcode.net/pythoncr/ # python_version :3.9 # ============================================================================== import random from bodies import Sun, Earth, FixedStar from sim_scenes.universe_sim_scenes import UniverseSimScenes from simulators.ursina.entities.entity_utils import create_ambient_light from bodies.universe_body import create_universe_body from common.consts import SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR, AU from objs import Obj, SpaceShip, StarWarsSpeeder, ScifiGunship, SciFiBomber, CoreValagaClas from sim_scenes.func import mayavi_run, mpl_run, ursina_run, camera_look_at, two_bodies_colliding, camera_move_control, \ get_run_speed_factor, create_text_panel from ursina import camera, application, lerp, Vec3 from bodies import Body from simulators.ursina.ursina_event import UrsinaEvent from dataclasses import dataclass, field import inspect @dataclass(order=True) class WormholeInfo: # entity: object name: str = field(default='') position: list[float] = field(default_factory=[0, 0, 0]) camera_target_init_speed: int = 100 camera_init_speed: int = 100 camera_target_speed: int = 1200 camera_target_max_speed: int = 1500 camera_forward_speed: int = - 0.1 camera_max_speed: int = 1500 camera_acc_speed: int = 1.02 camera_forward_acc_speed: int = 0.01 size_factor: float = 16 rotation_x: float = None rotation_y: float = None rotation_z: float = None around_max_count: int = 400 around_max_speed: int = -100 around_min_speed: int = 100 around_direction: str = "right" around_acc_speed: int = -1.5 class WormholeSim(UniverseSimScenes): SIZE_SCALE = 1 D = AU / 10 * SIZE_SCALE def create_universes(self): D = self.D # camera_target_init_speed: int = 100 # camera_init_speed: int = 100 # camera_target_speed: int = 1200 # camera_target_max_speed: int = 1500 # camera_forward_speed: int = - 0.1 # camera_max_speed: int = 1500 # camera_acc_speed: int = 1.02 # camera_forward_acc_speed: int = 0.01 # size_factor: float = 16 # rotation_x: float = None # rotation_y: float = None # rotation_z: float = None # around_max_count: int = 400 # around_max_speed: int = -100 # around_min_speed: int = 100 # around_direction: str = "right" # around_acc_speed: int = -1.5 wormhole_infos = [ {"name": "我们宇宙", "position": [D, 0, -D], "rotation_y": 100, "around_direction": "down", "camera_forward_speed": 0, "around_max_count": 400}, {"name": "粉色宇宙", "position": [D, 0, D], "rotation_z": 0, "around_acc_speed": -2, "around_min_speed": 150, "camera_forward_speed": -0.2, "around_max_count": 700}, {"name": "蓝天宇宙", "position": [- D, -D, D], "around_direction": "left", "around_max_count": 600}, {"name": "金色宇宙", "position": [- D, 0, -D], "around_max_count": 600}, {"name": "深红宇宙", "position": [-D, D, D], "around_max_count": 800}, # {"position": [D, -D, D]}, # {"position": [- D, -D, -D]}, # {"position": [- D, 0, D]}, # {"position": [D, D, -D]}, # {"position": [- D, D, -D]}, # {"position": [- D, D, D]}, # {"position": [D, -D, -D]} ] def build_wormhole_info_param(wormhole_info): # ps = WormholeInfoParams() if self.wormholeinfo_default_vals is None: self.wormholeinfo_default_vals = {} init_wormholeinfo_default_vals = True else: init_wormholeinfo_default_vals = False ps = {} attributes = inspect.getmembers(WormholeInfo) for attr in attributes: if not attr[0].startswith('_'): pn = attr[0] if init_wormholeinfo_default_vals: self.wormholeinfo_default_vals[pn] = attr[1] if pn not in wormhole_info.keys(): continue ps[pn] = wormhole_info[pn] # attr[1].default return ps for idx, wormhole_info in enumerate(wormhole_infos): pos = wormhole_info.get("position", [0, 0, 0]) name = wormhole_info.get("name", f"宇宙{idx}") ps1 = build_wormhole_info_param(wormhole_info) ps1["position"] = pos ps1["name"] = name universe = self.create_universe(name, idx, pos) self.universes.append(universe) self.wormhole_infos[universe] = WormholeInfo(**ps1) def __init__(self): D = self.D self.camera_target = CoreValagaClas(name="摄像机镜头", mass=1e30, color=(111, 140, 255), # init_position=[0, 0, 0], init_position=[D, 0, -D], init_velocity=[0, 0, 0], size_scale=self.SIZE_SCALE * 1e3).set_ignore_gravity(True) self.wormholeinfo_default_vals = None self.camera_target.go_target = None self.camera_target.go_target_stage = None self.last_universe = None self.current_universe = None self.target_universe = None self.universes = [] self.fixed_stars = [] self.wormhole_infos = {} self.create_universes() self.create_fixed_stars() self.bodies = [self.camera_target] + self.universes + self.fixed_stars def create_universe(self, name, idx, position): if name is None: name = f"宇宙{idx}" universe_body = create_universe_body(name, texture=f'cosmic_pan_{idx + 1}.jpg', # self.textures[idx], size_scale=self.SIZE_SCALE, init_position=position, init_velocity=[0, 0, 0], ) universe_body.set_light_disable(True) return universe_body def fixed_stars_D(self): r = random.random() D = self.D * (2 + r) return D def create_fixed_stars(self): D = self.fixed_stars_D positions = [[D(), D(), D()], [D(), D(), -D()], [D(), -D(), D()], [D(), -D(), -D()], [-D(), D(), D()], [-D(), D(), -D()], [-D(), -D(), D()], [-D(), -D(), -D()]] # positions = [[D, D, D], [D, D, -D], [D, -D, D], [D, 0, D], [D, 0, - D], [D, 0, D], # [D, -D, -D], [-D, D, D], [-D, D, -D], [0, -D, -D], [0, D, D], [0, D, -D], # [-D, -D, D], [-D, -D, -D], [-D, -D, 0], [-D, -D, 0]] for position in positions: fixed_star = FixedStar(color=(200, 200, 200), size_scale=0.5, init_position=position) fixed_star.glows = (0, 1.1, 1.08) self.fixed_stars.append(fixed_star) def init_setting(self): camera.clip_plane_near = 0.6 # camera.clip_plane_near = 0.01 camera.clip_plane_far = 10000000 # create_sphere_sky(scale=200000) application.time_scale = 0.001 # camera.scale = 1000 # camera.parent = camera_target.planet # camera.rotation_x = 90 camera.fov = 120 for universe in self.universes: universe.planet.collider = "sphere" universe.planet.enabled = False camera.forward_speed = 0 def get_wormhole_data(self, data_name, default_val=None, universe=None): def ret_value(): if default_val is None: if data_name in self.wormholeinfo_default_vals.keys(): return self.wormholeinfo_default_vals[data_name] return default_val if universe is None: universe = self.current_universe wormhole_info = self.wormhole_infos[universe] if wormhole_info is None: return ret_value() if not hasattr(wormhole_info, data_name): return ret_value() val = getattr(wormhole_info, data_name) if val is None: return ret_value() return val def universe_reset(self): self.current_idx += 1 self.target_idx += 1 if self.current_idx > len(self.universes) - 1: self.current_idx = 0 if self.target_idx > len(self.universes) - 1: self.target_idx = 0 for u in self.universes: self.scale_down(u) u.planet.enabled = False self.last_universe = self.current_universe self.current_universe = self.universes[self.current_idx] self.target_universe = self.universes[self.target_idx] rotation_x: float = self.get_wormhole_data("rotation_x") rotation_y: float = self.get_wormhole_data("rotation_y") rotation_z: float = self.get_wormhole_data("rotation_z") if rotation_x is not None: self.current_universe.planet.rotation_x = rotation_x if rotation_y is not None: self.current_universe.planet.rotation_y = rotation_y if rotation_z is not None: self.current_universe.planet.rotation_z = rotation_z self.camera_target.speed = self.get_wormhole_data("camera_target_init_speed") camera.speed = self.get_wormhole_data("camera_init_speed") if self.last_universe is None: self.camera_target.go_target = self.target_universe self.camera_target.go_target_stage = "target_universe" self.camera_target.go_target_hit = False else: self.camera_target.go_target = self.last_universe self.last_universe.planet.enabled = True size_factor = self.get_wormhole_data("size_factor", universe=self.last_universe) self.scale_down(self.last_universe, scale_size=self.SIZE_SCALE * size_factor) self.camera_target.go_target_stage = "last_universe" self.camera_target.go_target_hit = False self.scale_down(u) self.camera_target.speed = self.get_wormhole_data("camera_target_speed") camera.forward_speed = self.get_wormhole_data("camera_forward_speed") self.scale_up(self.current_universe) # self.scale_down(self.target_universe) self.current_universe.planet.enabled = True # self.current_universe.planet.set_light_off() self.target_universe.planet.enabled = True # self.target_universe.planet.set_light_off() def body_update_reset(self): # for b in self.bodies: # b.planet.update = lambda: None self.camera_target.planet.update = lambda: None def on_ready(self): """ 事件绑定后,模拟器运行前会触发 @return: """ self.init_setting() # create_ambient_light(parent=camera) self.current_idx = -1 self.target_idx = 0 self.current_universe = None self.body_update_reset() self.universe_reset() # camera_look_at(self.camera_target) self.camera_target.planet.collider = "sphere" self.camera_target.planet.enabled = False camera.collider = "sphere" camera.current_universe = self.current_universe camera_look_at(self.camera_target, rotation_z=0) self.text_panel = create_text_panel(font="fonts/DroidSansFallback.ttf", font_scale=1.3) def update_text_panel(self): """ 更新文字信息面板 @param d_sun: @return: """ if not hasattr(self, "text_panel"): return panel_text = "虫洞穿越宇宙:\n\n当前宇宙:%s " % self.current_universe.name panel_text += "\n\n下站宇宙:%s" % self.target_universe.name if self.last_universe is not None: panel_text += "\n\n上站宇宙:%s " % self.last_universe.name self.text_panel.text = panel_text def scale_up(self, obj): if obj is None: return obj.planet.init_scale = self.SIZE_SCALE * 100 def scale_down(self, obj, scale_size=None): if obj is None: return if scale_size is None: scale_size = self.SIZE_SCALE obj.planet.init_scale = scale_size def around_universe(self, around_target): around_max_count = self.get_wormhole_data("around_max_count") around_max_speed = self.get_wormhole_data("around_max_speed") around_min_speed = self.get_wormhole_data("around_min_speed") around_direction = self.get_wormhole_data("around_direction") around_acc_speed = self.get_wormhole_data("around_acc_speed") # around_target = camera self.camera_target.planet.look_at(around_target) # print(self.camera_target.planet.rotation_x,self.camera_target.planet.rotation_y,self.camera_target.planet.rotation_z) planet_direction = getattr(self.camera_target.planet, around_direction) if not hasattr(self.camera_target, "around_speed"): self.camera_target.around_speed = 0 if not hasattr(self.camera_target, "around_count"): self.camera_target.around_count = 0 if self.camera_target.around_count > around_max_count: if hasattr(self.camera_target, "around_speed") and \ abs(self.camera_target.around_speed) > abs(around_min_speed): self.camera_target.around_speed -= around_acc_speed else: # delattr(self.camera_target, "around_speed") delattr(self.camera_target, "around_count") self.camera_target.go_target_stage = "last_universe" self.camera_target.speed = self.get_wormhole_data("camera_init_speed") else: self.camera_target.around_speed += around_acc_speed if hasattr(self.camera_target, "around_speed"): if abs(self.camera_target.around_speed) > abs(around_max_speed): self.camera_target.around_speed = around_max_speed self.camera_target.planet.position += planet_direction * self.camera_target.around_speed print("around_speed", self.camera_target.around_speed) if hasattr(self.camera_target, "around_count"): self.camera_target.around_count += 1 # camera.forward_speed = -0.05 if abs(camera.forward_speed) > 0.01: camera_forward_acc_speed = self.get_wormhole_data("camera_forward_acc_speed") camera.forward_speed += camera_forward_acc_speed # else: # camera.forward_speed = 0 def go_last_universe(self, go_target): if self.camera_target.planet.intersects(go_target).hit: if not self.camera_target.go_target_hit: self.camera_target.go_target_hit = True self.camera_target.go_target_stage = "around_universe" self.camera_target.go_target = self.target_universe self.camera_target.speed = self.get_wormhole_data("camera_init_speed") camera.speed = 1 # 在看上一个宇宙时候,停留一会 # # camera.forward_speed = -0.05 # if camera.forward_speed < 0: # camera_forward_acc_speed = self.get_wormhole_data("camera_forward_acc_speed") # camera.forward_speed += camera_forward_acc_speed # else: # camera.forward_speed = 0 if hasattr(self.camera_target, "around_speed"): if abs(self.camera_target.around_speed) > abs(10): around_acc_speed = self.get_wormhole_data("around_acc_speed") around_direction = self.get_wormhole_data("around_direction") planet_direction = getattr(self.camera_target.planet, around_direction) self.camera_target.around_speed -= around_acc_speed self.camera_target.planet.position += planet_direction * self.camera_target.around_speed else: delattr(self.camera_target, "around_speed") def on_timer_changed(self, time_data): self.target_universe.planet.rotation_y -= 0.5 if self.last_universe is not None: self.last_universe.planet.rotation_y += 0.3 camera_look_at(self.camera_target, rotation_z=0) # camera.rotation_x -= 2 # self.camera_target.planet.look_at(self.target_universe.planet) # self.camera_move(time_data) from ursina import time, distance camera_max_speed = self.get_wormhole_data("camera_max_speed") camera_acc_speed = self.get_wormhole_data("camera_acc_speed") camera_target_max_speed = self.get_wormhole_data("camera_target_max_speed", ) if self.camera_target.go_target is not None: go_target = self.camera_target.go_target.planet if self.camera_target.go_target_stage == "around_universe": # 摄像机镜头环绕目标宇运动(摄像机镜头暂时不去目标) self.around_universe(go_target) else: # 摄像机镜头看向目标宇宙,并逐渐靠近目标 self.camera_target.planet.look_at(go_target) self.camera_target.planet.position = \ lerp(self.camera_target.planet.position, go_target.position, self.camera_target.speed * time.dt) # 摄像机镜头加速度计算(摄像机镜头圆滑效果),并对摄像机镜头移动速度进行限制 self.camera_target.speed *= camera_acc_speed if self.camera_target.speed > camera_target_max_speed: self.camera_target.speed = camera_target_max_speed if self.camera_target.go_target_stage == "last_universe": # 进入新的宇宙后,摄像机回看上一个宇宙的镜头 self.go_last_universe(go_target) elif self.camera_target.go_target_stage == "target_universe": # 摄像机镜头去往新的宇宙进行中 if self.camera_target.planet.intersects(go_target).hit and not self.camera_target.go_target_hit: # 摄像机镜头达到新的目标宇宙后,摄像机就不后退了 self.camera_target.go_target_hit = True camera.forward_speed = 0 # camera.speed = 3000 # TODO:这个不能反复执行 # self.camera_target.planet.position += self.camera_target.planet.forward*100 # print(self.camera_target.planet.position) # 摄像机加速度计算(摄像机圆滑效果) camera.speed *= camera_acc_speed if camera.speed > camera_max_speed: camera.speed = camera_max_speed if camera.speed != 0: dd = distance(camera.position, self.target_universe.planet.position) camera.position = lerp(camera.position, self.target_universe.planet.position, camera.speed * time.dt / dd) if camera.forward_speed != 0: camera.position += camera.forward * camera.forward_speed # if self.camera_target.planet.intersects(self.target_universe.planet).hit: if camera.intersects(self.target_universe.planet).hit: self.universe_reset() self.update_text_panel() def build_events(self): # 订阅事件后,上面2个函数功能才会起作用 # 运行中,每时每刻都会触发 on_timer_changed UrsinaEvent.on_timer_changed_subscription(wormhole_sim.on_timer_changed) # 运行前会触发 on_ready UrsinaEvent.on_ready_subscription(wormhole_sim.on_ready) def run(self): self.set_window_size((1920, 1079), False) self.build_events() # 使用 ursina 查看的运行效果 # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 # position = 左-右+、上+下-、前+后- ursina_run(self.bodies, SECONDS_PER_DAY / 24, # position=(0, AU / 1200, -1.003 * AU), # position=[-AU/100, 0, 0], # position=[0, 0, -AU / 10], # position=[0, 0, ], position=[0, self.D, -self.D * 2], cosmic_bg='', gravity_works=False, # bg_music='sounds/no_glory.mp3', timer_enabled=True, video_recoder=True, show_exit_button=False, show_grid=False, show_camera_info=False, show_control_info=False, show_trail=False) if __name__ == '__main__': """ """ wormhole_sim = WormholeSim() wormhole_sim.run()