__main__.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import abc
  2. import pygame
  3. import pygame.draw
  4. import pygame.display as display
  5. import pygame.event
  6. import math
  7. import time
  8. assert not pygame.init()[-1], "初始化失败(pygame存在未初始化的模块)"
  9. on_time: float = 1000 # 一次计算的时间间隔( = 沙盘时间 / on_run)
  10. on_run: int = 20 # 运行 on_run 次,绘图一次
  11. world_time: float = 0.001 # 一个on_time * on_run等于实际的时间
  12. UA = 149597870700
  13. on_m: float = UA / 350 # 1UA的10分之一
  14. G = 6.67 * (10 ** -11) # 万有引力常数
  15. class Planet(metaclass=abc.ABCMeta):
  16. def __init__(self):
  17. self.name = ""
  18. self.center: Planet = None
  19. self.x = 0
  20. self.y = 0
  21. self.dx = 0 # 每次run的时候,增加的x
  22. self.dy = 0
  23. self.r = 0 # 星球半径
  24. self.center_r = 0 # 距离中心天体的半径
  25. self.color = (0, 0, 0)
  26. self.m = 0
  27. def set_m(self, m: float):
  28. self.m = m
  29. def set_center_r(self, r: float) -> None:
  30. self.center_r = r
  31. def set_r(self, r: float) -> None:
  32. self.r = r
  33. def set_color(self, color: tuple) -> None:
  34. self.color = color
  35. @abc.abstractmethod
  36. def draw(self, dx, dy, max_x, max_y, is_center=True, is_draw=True): # dx和dy是偏移量
  37. pass
  38. def get_xy(self): # dx和dy是偏移量
  39. return self.x, self.y
  40. def get_dxy(self): # dx和dy是偏移量
  41. return self.dx, self.dy
  42. def set_name(self, name) -> None: # dx和dy是偏移量
  43. self.name = name
  44. def set_center(self, center) -> None: # dx和dy是偏移量
  45. self.center: Planet = center
  46. def get_center_xy(self): # dx和dy是偏移量
  47. return self.center.get_xy()
  48. def run(self): # dx和dy是偏移量
  49. return self.get_xy()
  50. def get_name(self): # dx和dy是偏移量
  51. return self.name
  52. def draw_name(self, dx, dy):
  53. pass
  54. class Sun(Planet):
  55. def __init__(self):
  56. super(Sun, self).__init__()
  57. self.set_name("Sun")
  58. self.color = (255, 0, 0)
  59. def draw(self, dx, dy, max_x, max_y, is_center=True, is_draw=True) -> None: # dx和dy是偏移量
  60. if not is_draw:
  61. return
  62. dx = int(dx / on_m)
  63. dy = int(dy / on_m)
  64. if (0 <= dx < max_x and 0 <= dy < max_y) or is_center:
  65. self.draw_name(dx, dy)
  66. r = int(self.r / on_m)
  67. pygame.draw.circle(screen, self.color, (dx, dy), r, 0)
  68. class RunPlanet(Planet):
  69. def __init__(self):
  70. super(RunPlanet, self).__init__()
  71. self.sita = 0 # 与中心天体的角度
  72. self.last_sita = 0
  73. self.v = 0 # 线速度
  74. self.v_type = 1 # 0-角速度,1-线速度
  75. def setting(self, center, m, r, color, center_r, first=True):
  76. self.set_center(center)
  77. self.set_color(color)
  78. self.set_r(r)
  79. self.set_m(m)
  80. self.set_center_r(center_r)
  81. if first:
  82. cx, cy = center.get_xy()
  83. self.y = cy
  84. self.x = cx + self.center_r
  85. self.r_to_v()
  86. def r_to_v(self):
  87. v = math.sqrt(G * self.center.m / self.center_r)
  88. self.set_v(v, 1)
  89. def set_v(self, v: float, v_type: int = 1) -> None:
  90. self.v = v
  91. self.v_type = v_type
  92. def draw(self, dx, dy, max_x, max_y, is_center=True, is_draw=True): # dx和dy是偏移量
  93. self.run()
  94. if not is_draw:
  95. return
  96. if is_center:
  97. dx += -self.x
  98. dy += -self.y
  99. x = int((self.x + dx) / on_m)
  100. y = int((self.y + dy) / on_m)
  101. if 0 <= x < max_x and 0 <= y < max_y:
  102. self.draw_name(dx, dy)
  103. r = int(self.r / on_m)
  104. pygame.draw.circle(screen, self.color, (x, y), r, 0)
  105. def run(self):
  106. cx, cy = self.center.get_xy()
  107. # 保持与中心天体相对静止
  108. self.dx, self.dy = self.center.get_dxy()
  109. sx, sy = self.get_xy()
  110. sx += self.dx
  111. sy += self.dy
  112. dx, dy = sx - cx, sy - cy # 两者距离
  113. # self.set_center_r(math.sqrt(dx ** 2 + dy ** 2))
  114. # self.r_to_v()
  115. if dx == 0: # 同一竖直线上
  116. if dy > 0: # plant在center下面
  117. vx = self.v
  118. else:
  119. vx = -self.v
  120. vy = 0
  121. elif dy == 0: # 同一竖直线上
  122. if dx > 0: # plant在center下面
  123. vy = -self.v
  124. else:
  125. vy = self.v
  126. vx = 0
  127. else:
  128. new_sita = self.get_sita(dx, dy)
  129. center_sita = (new_sita - self.sita) / 2 # 取得前进的一半
  130. if self.sita > 0 and new_sita < 0: # 跨越了180度界限
  131. center_sita = math.pi - center_sita
  132. vx = math.cos(new_sita + center_sita) * self.v # new_sita向前一半(保证r不会慢慢变大)
  133. vy = -math.sin(new_sita + center_sita) * self.v
  134. self.last_sita = self.sita
  135. self.sita = new_sita
  136. self.dx += vx * on_time
  137. self.dy += vy * on_time
  138. self.x += self.dx
  139. self.y += self.dy
  140. super(RunPlanet, self).run()
  141. @staticmethod
  142. def get_sita(dx, dy):
  143. if dx > 0 and dy > 0: # 右下
  144. tan_sita = dx / dy
  145. sita = math.atan(tan_sita)
  146. elif dx > 0 and dy < 0: # 右上
  147. tan_sita = -dy / dx
  148. sita = math.atan(tan_sita) + 0.5 * math.pi # +90度
  149. elif dx < 0 and dy > 0: # 左下
  150. tan_sita = -dx / dy
  151. sita = -math.atan(tan_sita)
  152. else: # 左上
  153. tan_sita = -dy / -dx
  154. sita = -math.atan(tan_sita) - 0.5 * math.pi # +90度
  155. return sita
  156. def set_sita(self):
  157. cx, cy = self.center.get_xy()
  158. sx, sy = self.get_xy()
  159. dx, dy = sx - cx, sy - cy
  160. sin_sita = dx / dy
  161. self.sita = math.asin(sin_sita)
  162. # self.sita += new_sita
  163. # if self.sita >= 2 * math.pi:
  164. # self.sita = 0
  165. class WorldControl:
  166. def __init__(self, dx=0, dy=0):
  167. self.plant = {
  168. "Sun": None,
  169. "Earth": ("Sun", 5.965 * (10 ** 24), 3185696.5, (0, 255, 255), UA),
  170. "Mercury": ("Sun", 3.3 * (10 ** 23), 244 * 10 ** 4, (0, 255, 255), 0.387 * UA),
  171. "Mars": ("Sun", 6.4219 * (10 ** 23), 3397 * 10 ** 3, (0, 255, 255), 0.62 * UA),
  172. "Venus": ("Sun", 4.869 * (10 ** 24), 6051.8 * 10 ** 3, (0, 255, 255), 0.72 * UA),
  173. "Moon": ("Earth", 7.349 * (10 ** 22), 1738140, (0, 255, 0), 3844 * 10 ** 5),
  174. # 质量 半径 颜色 距中心天体半径
  175. }
  176. self.plant_list = []
  177. for name in self.plant:
  178. if name == "Sun":
  179. sun = Sun()
  180. sun.set_m(2 * (10 ** 30))
  181. sun.set_r(696300000)
  182. self.plant_list.append(sun)
  183. self.plant[name] = sun
  184. else:
  185. tmp = RunPlanet()
  186. tmp.setting(self.plant[self.plant[name][0]], *(self.plant[name][1:]))
  187. tmp.set_name(name)
  188. self.plant_list.append(tmp)
  189. self.plant[name] = tmp
  190. self.is_move = False
  191. self.dx, self.dy = dx * on_m, dy * on_m # 化成沙盒尺寸
  192. self.mx, self.my = dx * 2, dx * 2
  193. self.dbx, self.dby = self.dx, self.dy
  194. self.pos = None # 上一次pos
  195. self.dm = UA / 3500
  196. self.base_dm = UA / 3500
  197. self.center = 0 # 跟踪对象
  198. def draw(self) -> None:
  199. draw_count = 0
  200. while True:
  201. is_draw = bool(draw_count == 0)
  202. if is_draw:
  203. screen.fill((0, 0, 0))
  204. self.plant_list[self.center].draw(self.dx, self.dy, self.mx, self.my, is_center=True, is_draw=is_draw) # 先画参考对象
  205. x, y = self.plant_list[self.center].get_xy() # 获取偏移
  206. for p in self.plant_list[::-1]:
  207. if p == self.plant_list[self.center]: # 不绘制参考对象(已经绘制过了)
  208. continue
  209. p.draw(self.dx - x, self.dy - y, self.mx, self.my, is_center=False, is_draw=is_draw)
  210. if is_draw:
  211. display.update()
  212. self.event(pygame.event.get())
  213. time.sleep(world_time)
  214. if is_draw:
  215. draw_count = on_run
  216. else:
  217. draw_count -= 1
  218. def new_on_m(self, old, new):
  219. dx = self.dx / old
  220. dy = self.dy / old
  221. dbx = self.dbx / old
  222. dby = self.dby / old
  223. self.dx = dx * new
  224. self.dy = dy * new
  225. self.dbx = dbx * new
  226. self.dby = dby * new
  227. def event(self, event_list: list):
  228. global on_m
  229. for event in event_list:
  230. if event.type == pygame.QUIT:
  231. exit(0)
  232. # 鼠标按下,让状态变成可以移动
  233. elif event.type == pygame.MOUSEBUTTONDOWN:
  234. self.is_move = True
  235. self.pos = event.pos
  236. # 鼠标弹起,让状态变成不可以移动
  237. elif event.type == pygame.MOUSEBUTTONUP:
  238. if event.button == 4:
  239. self.is_move = False
  240. old = on_m
  241. while self.dm >= 1:
  242. on_m -= self.dm
  243. if on_m <= 0:
  244. on_m += self.dm
  245. self.dm /= 2
  246. else:
  247. break
  248. # 调整(dx, dbx等均是沙盒距离)
  249. self.new_on_m(old, on_m)
  250. elif event.button == 5:
  251. self.is_move = False
  252. old = on_m
  253. on_m += self.dm
  254. if self.dm < self.base_dm:
  255. self.dm *= 2
  256. # 调整(dx, dbx等均是沙盒距离)
  257. self.new_on_m(old, on_m)
  258. else:
  259. self.is_move = False
  260. self.pos = None
  261. # 鼠标移动事件
  262. elif event.type == pygame.MOUSEMOTION:
  263. if self.is_move:
  264. screen.fill((255, 255, 255))
  265. x, y = event.pos
  266. if self.pos:
  267. self.dx += (x - self.pos[0]) * on_m
  268. self.dy += (y - self.pos[1]) * on_m
  269. self.pos = event.pos
  270. elif event.type == pygame.KEYDOWN:
  271. if event.key == pygame.K_g:
  272. self.dx = self.dbx
  273. self.dy = self.dby
  274. elif event.key == pygame.K_DOWN:
  275. if self.center == 0:
  276. self.center = len(self.plant_list) - 1
  277. else:
  278. self.center -= 1
  279. elif event.key == pygame.K_UP:
  280. if self.center == len(self.plant_list) - 1:
  281. self.center = 0
  282. else:
  283. self.center += 1
  284. if __name__ == "__main__":
  285. center = (1200, 400)
  286. screen = display.set_mode((center[0] * 2, center[1] * 2), 0, 32)
  287. display.set_caption("物理: 天体运动")
  288. world = WorldControl(*center)
  289. world.draw()