# -*- coding:utf-8 -*- # title :木星、月球保护地球模拟 # description :木星、月球保护地球模拟 # author :Python超人 # date :2023-07-11 # link :https://gitcode.net/pythoncr/ # python_version :3.8 # ============================================================================== import time from bodies import Sun, Jupiter, Earth, Moon from common.func import calculate_distance from objs import RockSnow, Rock, create_rock from common.consts import SECONDS_PER_MONTH, SECONDS_PER_YEAR, AU from sim_scenes.func import ursina_run, camera_look_at, two_bodies_colliding, create_text_panel, create_sphere_sky from sim_scenes.universe_sim_scenes import UniverseSimScenes from simulators.ursina.entities.body_timer import TimeData, BodyTimer from simulators.ursina.entities.entity_utils import create_directional_light from simulators.ursina.ursina_config import UrsinaConfig from simulators.ursina.ursina_event import UrsinaEvent import random import math import numpy as np class JupiterMoonProtectsEarthSim(UniverseSimScenes): def __init__(self, comet_num=20): """ @param comet_num: 随机生成 comet_num 个石头 """ self.earth_moon_d = 30000000 # 因为地球放大 2000 倍,为了较好的效果,地月距离要比实际大才行 self.sun_earth_d = 200000000 self.sun_jupiter_d = 520000000 # 分别保存太阳碰撞次数、木星碰撞次数、地球碰撞次数、月球碰撞次数、木星保护次数、月球保护次数 self.colliding_count = [0, 0, 0, 0, 0, 0] self.sun = Sun(name="太阳", size_scale=0.85e2, show_trail=False) # 太阳放大 80 倍,距离保持不变 self.jupiter = Jupiter(name="木星", size_scale=0.45e3, ignore_mass=False, show_trail=False) # 木星放大 600 倍,距离保持不变 self.earth = Earth(name="地球", size_scale=2.5e3, ignore_mass=False, show_trail=False ) # 地球放大 2000 倍,距离保持不变 self.moon = Moon(name="月球", size_scale=3.5e3, # 月球球放大 3000 倍,为了较好的效果,地月距离要比实际大 init_position=[self.earth_moon_d, 0, AU], init_velocity=[0, 0, 0], ignore_mass=True, rotation_speed=0.4065, show_trail=False, # gravity_only_for_earth=True ) # .set_light_disable(True) self.bodies = [self.sun, self.jupiter, self.earth, self.moon] for body in self.bodies: body.rotation_speed /= 10 # 自转速度降低10倍 self.comets = [] self.comet_radius = self.sun_jupiter_d * 1.5 # calculate_distance(self.jupiter.position, self.sun.position) # * 1.5 for i in range(comet_num): # 随机生成 comet_num 个石头 comet = self.create_comet(i, gravity_only_for=[self.sun, self.earth, self.jupiter]) # comet = self.create_comet(i, gravity_only_for=[self.sun, self.jupiter, self.earth]) self.bodies.append(comet) self.comets.append(comet) # 显示板信息模板 self.colliding_info = "陨石碰撞(次数)\n\n - 太阳: %s次(%s)\n\n - 木星: %s次(%s)\n\n - 地球: %s次(%s)\n\n - 月球: %s次(%s)" \ "\n\n保护地球(次数)\n\n - 木星: %s次\n\n - 月球: %s次\n\n" def random_pos_vel(self): sun_pos = self.sun.position # # 随机生成石头的位置和初始速度信息 radius = self.comet_radius * (random.randint(100, 150) / 100) rnd_angle = random.randint(0, 360) # x = radius * math.cos(random.uniform(0, 2 * math.pi)) * (random.randint(100, 150) / 100) # z = radius * math.sin(random.uniform(0, 2 * math.pi)) * (random.randint(100, 150) / 100) angle = math.pi * rnd_angle / 180 x = sun_pos[0] + radius * math.cos(angle) z = sun_pos[2] + radius * math.sin(angle) pos = [x, 0 + sun_pos[1], z] # 随机速度 vel = [-random.randint(90, 200) / 300, 0, 0] vel = [0, 0, 0] return pos, vel def create_comet(self, index, gravity_only_for): """ 随机生成石头(随机位置、随机初始速度、随机大小、随机旋转) @param index: 索引号 @param gravity_only_for: 指定多个天体,石头只对该天体引力有效 @return: """ # 随机生成石头的位置和初始速度信息 pos, vel = self.random_pos_vel() # vel = [0, 0, 0] # 石头随机大小 size_scale = random.randint(500, 600) * 1.5e4 # 随机创建石头 rock = create_rock( no=index % 7 + 1, name=f'岩石{index + 1}', mass=size_scale / 1000, size_scale=size_scale, color=(255, 200, 0), init_position=pos, init_velocity=vel, gravity_only_for=gravity_only_for ) rock.set_light_disable(True) rock.trail_color = (255, 255, 255) # 给石头一个随机旋转的方向和值 rock.rotation = [0, 0, 0] rock.rotation[random.randint(0, 2)] = random.randint(90, 200) / 100 rock.set_light_disable() return rock def set_moon_position(self, time_data): # 1个月有29.5天 days_per_month = 29.5 # 1天多少角度 angle_per_day = 360 / days_per_month # 当前天数的角度(度) angle = time_data.total_days * angle_per_day # 当前天数的角度(弧度) angle = angle * np.pi / 180 # 计算月亮的坐标(这里没有用到万有引力) px = self.earth_moon_d * np.cos(angle) pz = self.earth_moon_d * np.sin(angle) self.moon.position = [self.earth.position[0] + px, 0, self.earth.position[2] + pz] def set_planet_position(self, planet, time_data, month_per_year, d): # 1个月有29.5天 days_per_month = 29.5 * month_per_year # 1天多少角度 angle_per_day = 360 / days_per_month # 当前天数的角度(度) angle = time_data.total_days * angle_per_day # 当前天数的角度(弧度) angle = angle * np.pi / 180 # 计算月亮的坐标(这里没有用到万有引力) px = d * np.cos(angle) pz = d * np.sin(angle) planet.position = [self.sun.position[0] + px, 0, self.sun.position[2] + pz] def on_timer_changed(self, time_data: TimeData): self.set_planet_position(self.earth, time_data, 12, self.sun_earth_d) self.set_planet_position(self.jupiter, time_data, 12 * 11.8622, self.sun_jupiter_d) self.set_moon_position(time_data) # 运行中,每时每刻都会触发 for comet in self.comets: if comet.planet.enabled: collided = False # 如果是否可见,则旋转石头 comet.planet.rotation += comet.rotation # 循环判断每个石头与木星是否相碰撞,如果相碰撞就爆炸 if not hasattr(comet, "jupiter_collided") and two_bodies_colliding(comet, self.jupiter): # 碰撞到木星,该石头不要爆炸,尝试看看是否会碰撞到地球,如果碰撞了地球,则“木星保护”统计加一 self.colliding_count[1] += 1 # 加入标记,说明该石头已经碰撞到木星 setattr(comet, "jupiter_collided", True) # collided = True elif not hasattr(comet, "moon_collided") and two_bodies_colliding(comet, self.moon): # 碰撞到月球,该石头不要爆炸,尝试看看是否会碰撞到地球,如果碰撞了地球,则“月球保护”统计加一 self.colliding_count[3] += 1 # 加入标记,说明该石头已经碰撞到月球 setattr(comet, "moon_collided", True) elif two_bodies_colliding(comet, self.sun): # 将石头隐藏、设置引力无效后,展示爆炸效果 comet.explode(self.sun) if not hasattr(comet, "jupiter_collided") and not hasattr(comet, "moon_collided"): # 该石头没有碰撞到木星和月球,才算一次 self.colliding_count[0] += 1 collided = True elif two_bodies_colliding(comet, self.earth): if hasattr(comet, "jupiter_collided") or hasattr(comet, "moon_collided"): if hasattr(comet, "jupiter_collided"): # 说明该石头已经碰撞到木星,也就是说木星保护了地球一次 self.colliding_count[4] += 1 else: # 说明该石头已经碰撞到月球,也就是说月球保护了地球一次 self.colliding_count[5] += 1 else: # 该石头没有碰撞到木星和月球,才算一次 self.colliding_count[2] += 1 comet.explode(self.earth) collided = True if collided: # 如果碰撞了,则该石头重复再利用,这样才保证有无限个石头可用 pos, vel = self.random_pos_vel() # comet.planet.trails.clear() # if hasattr(comet.planet, "trail_last_pos"): # delattr(comet.planet, "trail_last_pos") # if hasattr(comet.planet, "last_trail"): # del comet.planet.last_trail # delattr(comet.planet, "last_trail") comet.init_position = pos comet.init_velocity = vel comet.set_visible(True) comet.ignore_mass = False comet.planet.enabled = True if hasattr(comet, "jupiter_collided"): delattr(comet, "jupiter_collided") if hasattr(comet, "moon_collided"): delattr(comet, "moon_collided") comet.planet.on_reset() # print("Sun:%s Jupiter:%s Earth:%s Moon:%s" % (self.colliding_count[0], # self.colliding_count[1], # self.colliding_count[2], # self.colliding_count[3])) sun_cnt, jupiter_cnt, earth_cnt, moon_cnt, j_protected_cnt, m_protected_cnt = self.colliding_count total_cnt = sun_cnt + jupiter_cnt + earth_cnt + moon_cnt if total_cnt == 0: colliding_info = self.colliding_info % (0, "0.0%", 0, "0.0%", 0, "0.0%", 0, "0.0%", 0, 0) else: colliding_info = self.colliding_info % (sun_cnt, "%.1f" % (sun_cnt * 100 / total_cnt) + "%", jupiter_cnt, "%.1f" % (jupiter_cnt * 100 / total_cnt) + "%", earth_cnt, "%.1f" % (earth_cnt * 100 / total_cnt) + "%", moon_cnt, "%.1f" % (moon_cnt * 100 / total_cnt) + "%", j_protected_cnt, m_protected_cnt) ctime = time.time() if m_protected_cnt >= 8 and j_protected_cnt >= 2: if not hasattr(self, "m_protected_8_time"): self.m_protected_8_time = ctime else: if ctime - self.m_protected_8_time > 30: print("满足条件退出") exit() if ctime - self.run_begin_time > 700: print("没有满足条件退出") exit() self.text_panel.text = colliding_info # self.camera_move(time_data) def camera_move(self, time_data): from ursina import camera camera.position += camera.left * 1 camera_look_at(self.sun, rotation_z=0) def on_ready(self): from ursina import application, window # window.title = '宇宙模拟器(universe sim)' # 运行前触发 self.text_panel = create_text_panel(font="fonts/DroidSansFallback.ttf", font_scale=1.3) self.text_panel.text = self.colliding_info % (0, "0.0%", 0, "0.0%", 0, "0.0%", 0, "0.0%", 0, 0) camera_look_at(self.sun) self.sky = create_sphere_sky(scale=8000, texture="bg_pan2.jpg") self.sky.rotation_y = -180 self.sky.rotation_x = -20 # self.sky.rotation_z = -65 application.time_scale = 3 self.run_begin_time = time.time() UrsinaConfig.trail_type = "line" # UrsinaConfig.trail_length = 91 UrsinaConfig.trail_type = "curve_line" UrsinaConfig.trail_length = 10 # UrsinaConfig.trail_length = 1000 UrsinaConfig.trail_thickness_factor = 2 # for b in self.bodies: # if isinstance(b, Rock): # print("岩石大小", b.name, b.planet.scale_x) if __name__ == '__main__': """ 木星、月球保护地球模拟 """ # 设置计时器的最小时间单位为年 BodyTimer().min_unit = BodyTimer.MIN_UNIT_YEARS # UrsinaConfig.trail_type = "curve_line" # # UrsinaConfig.trail_type = "line" # UrsinaConfig.trail_length = 30 sim = JupiterMoonProtectsEarthSim(comet_num=30) # 运行前会触发 on_ready UrsinaEvent.on_ready_subscription(sim.on_ready) # 运行中,每时每刻都会触发 on_timer_changed UrsinaEvent.on_timer_changed_subscription(sim.on_timer_changed) # 使用 ursina 查看的运行效果 # 常用快捷键: P:运行和暂停 O:重新开始 I:显示天体轨迹 # position = 左-右+、上+下-、前+后- ursina_run(sim.bodies, SECONDS_PER_MONTH / 1, # position=(AU, AU * 5, -AU * 5), position=(0, 0, -AU * 6), cosmic_bg='', show_grid=False, # show_exit_button=False, # show_camera_info=False, show_control_info=False, timer_enabled=True)