# -*- coding:utf-8 -*- # title :虫洞效果模拟 # description :虫洞效果模拟 # author :Python超人 # date :2023-11-19 # link :https://gitcode.net/pythoncr/ # python_version :3.9 # ============================================================================== from bodies import Sun, Earth from common import color_utils from common.image_utils import find_texture 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 from ursina import time, distance, invoke from ursina import Vec3 from bodies import Body from simulators.ursina.ursina_event import UrsinaEvent from simulators.ursina.ursina_mesh import create_circle, create_line class WormholeSim: SIZE_SCALE = 1 D = AU / 10 * SIZE_SCALE 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 * 1e2).set_ignore_gravity(True) 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.create_universes() self.bodies = [self.camera_target] + self.universes def create_universe(self, idx, position): universe_body = create_universe_body(f"宇宙{idx}", texture=f'cosmic_pan_{idx + 1}.jpg', # self.textures[idx], size_scale=self.SIZE_SCALE, init_position=position, init_velocity=[0, 0, 0], ) return universe_body def create_universes(self): D = self.D positions = [[D, 0, -D], [D, 0, D], [- D, 0, -D], [- D, 0, D], [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, 0, -D], [D, 0, D], [- D, -D, D], [- D, 0, -D], [D, D, D], [D, -D, D], [- D, -D, -D], [- D, 0, D], [D, D, -D], [- D, D, -D], [- D, D, D], [D, -D, -D] ] for idx, pos in enumerate(positions): self.universes.append(self.create_universe(idx, pos)) def init_setting(self): camera.clip_plane_near = 0.00005 # camera.clip_plane_near = 0.01 camera.clip_plane_far = 100000000 # create_sphere_sky(scale=200000) application.time_scale = 0.001 # camera.scale = 1000 # camera.parent = camera_target.planet # camera.rotation_x = 90 camera.fov = 100 for universe in self.universes: universe.planet.collider = "sphere" universe.planet.enabled = False camera.forward_speed = 0 def show_wormhole_portal(self, position): if not hasattr(camera, "wormhole_portal"): wormhole_portal = self.create_wormhole_portal(position) camera.wormhole_portal = wormhole_portal camera.wormhole_portal.alpha = 0 camera.wormhole_portal.enabled = True camera.wormhole_portal.update_fun = "" if camera.wormhole_portal.update_fun == "show_update": return def show_update(): camera.wormhole_portal.look_at(camera) if camera.wormhole_portal.alpha < 1: camera.wormhole_portal.alpha += 0.05 camera.wormhole_portal.enabled = True camera.wormhole_portal.position = position camera.wormhole_portal.update = show_update camera.wormhole_portal.update_fun = "show_update" def hide_wormhole_portal(self): def hide_update(): camera.wormhole_portal.alpha -= 0.001 # camera.wormhole_portal.alpha -= 0.01 # if camera.wormhole_portal.alpha <= 0: # camera.wormhole_portal.enabled = False if hasattr(camera, "wormhole_portal"): camera.wormhole_portal.update_fun = "hide_update" camera.wormhole_portal.enabled = False if camera.wormhole_portal.update_fun == "hide_update": return pp = self.get_intersection_point(self.last_universe.planet.position, self.last_universe.planet.scale, camera.position) if pp is None: return camera.wormhole_portal.position = pp camera.wormhole_portal.look_at(camera) camera.wormhole_portal.update = hide_update camera.wormhole_portal.update_fun = "hide_update" def create_wormhole_portal(self, position): portal_circle = create_circle(parent=None, pos=position, scale=2) # portal_circle.texture = find_texture("fixed_star_glow.png") portal_circle.texture = find_texture("two_way_foil_circle.png") # glow_circle.color = color.white portal_circle.set_light_off(True) def glow_circle_update(): # glow_circle.enabled = fixed_star.enabled portal_circle.look_at(camera) portal_circle.update = glow_circle_update return portal_circle def get_intersection_point(self, center, r, pt): """ 计算线段和球体的交点坐标 Args: x0, y0, z0: 球体的中心坐标 r: 球体的半径 x1, y1, z1: 线段的两个端点坐标 Returns: 线段和球体的交点坐标,如果没有交点,则返回 None """ import math x0, y0, z0, x1, y1, z1 = center[0], center[1], center[2], pt[0], pt[1], pt[2] r = r[0] # 计算线段的方向向量 dx = x1 - x0 dy = y1 - y0 dz = z1 - z0 # 计算线段的长度 l = math.sqrt(dx ** 2 + dy ** 2 + dz ** 2) # 计算球心到线段的距离 d = math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2 + (z0 - z1) ** 2) # 如果球心到线段的距离小于球体的半径,则有交点 if d < r: return None # 计算线段和球体的交点坐标 x = x0 + dx * (r ** 2 - d ** 2) / (l ** 2) y = y0 + dy * (r ** 2 - d ** 2) / (l ** 2) z = z0 + dz * (r ** 2 - d ** 2) / (l ** 2) return Vec3(x, y, z) def check_and_create_wormhole_portal(self): d = distance(camera, self.target_universe) center = lerp(camera.position, self.target_universe.planet.position, 0.5) if d < 21156327: # direction = self.target_universe.planet.position - camera.position # tca = direction.dot(self.target_universe.planet.position - camera.position) # sum(direction ** 2) - tca ** 2 # line = create_line(from_pos=camera.position, to_pos=self.target_universe.planet.position) # #line.get_intersection(self.target_universe.planet) pp = self.get_intersection_point(self.target_universe.planet.position, self.target_universe.planet.scale, camera.position) self.target_universe.planet.alpha = 0.2 # application.paused = True print(d, center) self.show_wormhole_portal(position=pp) def universe_reset(self): self.camera_target.speed = 100 camera.speed = 100 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] 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 self.scale_down(self.last_universe, scale_size=self.SIZE_SCALE * 10) self.camera_target.go_target_stage = "last_universe" self.camera_target.go_target_hit = False self.scale_down(u) self.camera_target.speed = 2000 camera.forward_speed = -0.1 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() self.hide_wormhole_portal() # invoke(self.hide_wormhole_portal, delay=0.1) 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() # self.target_universe.planet.rotation_y = 100 # self.current_universe.planet.rotation_y = 200 # self.current_universe.planet.rotation_x = 30 # 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: """ 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 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) self.check_and_create_wormhole_portal() if self.camera_target.go_target is not None: go_target = self.camera_target.go_target.planet self.camera_target.planet.look_at(go_target) self.camera_target.speed *= 1.02 if self.camera_target.speed > 2000: self.camera_target.speed = 2000 self.camera_target.planet.position = \ lerp(self.camera_target.planet.position, go_target.position, self.camera_target.speed * time.dt) if self.camera_target.go_target_stage == "last_universe": 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 == "target_universe" self.camera_target.go_target = self.target_universe self.camera_target.speed = 100 camera.speed = 1 # 在看上一个宇宙时候,停留一会 # camera.forward_speed = -0.05 if camera.forward_speed < 0: camera.forward_speed += 0.01 else: camera.forward_speed = 0 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 *= 1.02 if camera.speed > 5000: camera.speed = 5000 dd = distance(camera.position, self.target_universe.planet.position) if camera.speed != 0: 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.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, 0], # position=[0, self.D, -self.D * 2], cosmic_bg='', gravity_works=False, timer_enabled=True, show_grid=False, show_trail=False) if __name__ == '__main__': """ """ wormhole_sim = WormholeSim() wormhole_sim.run()