Pull to refresh

Моделирование движения космических объектов (симулятор гравитации)

Level of difficultyEasy
Reading time6 min
Views5.5K

Как часто хочется себе представить как движения планет зависят от звезды? Сегодня для этого не нужен телескоп! Представить движения планет в зависимости от объекта очень большой массы можно и на компьютере.

Идея для данной программы зародилась в момент, когда я захотел наглядно представить себе то, как движутся планеты нашей солнечной системы, и как бы менялась траектория движения планет относительно солнца при различных начальных условиях.

Долго тянуть не буду и сразу перейду к краткому описанию программы. Решил писать на питоне. Суть заключается в том, что мы можем создавать планеты в любой точке плоскости, с любым радиусом, массой, цветом и начальной скоростью и моделировать их взаимодействие.Чтобы было не очень сумбурно, решил в куски с кодом вставлять комментарии.

Сначала продемонстрирую пример работы: создаю Солнце и 6 планет, левым придаю скорость вверх, а правым - вниз, равную по модулю. Несмотря на то, что в начальный момент скорость каждой из планет одинакова, свой полный оборот вокруг Солнца они завершают в разное время. Это происходит из-за разницы в начальных расстояниях.

Первый блок: просто импортируем необходимые библиотеки

import pygame
import time

Второй блок: создаём необходимые переменные

fps = 120  # просто fps
G = 6.67 / 10 ** 11  # сохраняем константу гравитационной постоянной
k = 1000000  # метров в одном пикселе
mouse_x, mouse_y = 0, 0  # начальное положение мышки (начальные значения не очень влияют)
time_speed = 10000  # коэффициент ускорения программы
# При коэффициенте ускорения больше 10 ^ 5, появляется очень большая погрешность

Третий блок: запускаем окно, задаём ширину, высоту и переменные для отображения fps и ускорения

pygame.init()
screen = pygame.display.set_mode((1500, 800))
pygame.display.set_caption("Planetary acceleration")
style = pygame.font.SysFont("arial", 36)
render_fps = style.render('fps ' + str(fps), True, 'blue')
render_time_speed = style.render('acceleration: ' + str(time_speed), True, 'blue')

Четвёртый блок: для удобства создадим класс, который будет отвечать за одну планету

class Planet:
    def __init__(self, x, y, r, m, color):  # задаём начальные параметры
        self.x = x  # координата по оси Ох
        self.y = y  # координата по оси Оу
        self.r = r  # радиус планеты
        self.m = m  # масса планеты
        self.color = color  # цвет планеты
        self.speed = [0, 0]  # скорость которую имеет планета 
        self.f = [0, 0]  # сила, которые действуют на планету
        self.status = True  # существует планета или уже нет
        self.trace_count = 0  # переменная, чтобы "не очень часто" выводить траекторию планеты, иначе сильно упало бы fps
        self.trace = []  # точки траектории нашей планеты

    def update_coordinates(self):  # обновляем скорость, координату и траекторию
        self.speed[0] += (self.f[0] / self.m) * time_speed ** 2 / fps ** 2 
        self.speed[1] += (self.f[1] / self.m) * time_speed ** 2 / fps ** 2

        self.x += self.speed[0]
        self.y += self.speed[1]

        self.trace_count += (self.speed[0] ** 2 + self.speed[1] ** 2) ** 0.5 
        if self.trace_count / k >= 7:  # чем меньше число после знака, тем более сплошной будет траектория при выводе
            self.trace_count = 0
            self.trace.append((self.x, self.y))
        if len(self.trace) > 1000:  # Также, чтобы не проседало fps, когда много точек, начинаем удалять с конца
            self.trace.pop(0)

    def draw(self):  # отображаем все планеты и их траектории
        pygame.draw.circle(screen, self.color, ((self.x - mouse_x) / k, (self.y - mouse_y) / k), self.r / k)
        for i in self.trace:
            pygame.draw.circle(screen, self.color, ((i[0] - mouse_x) / k, (i[1] - mouse_y) / k), 1)

Пятый блок: нужен для определения всех сил, действующих на каждую планету

def update_forces(planets, collides):
    for i in range(len(planets)):
        for j in range(i + 1, len(planets)):
            dif_x = planets[j].x - planets[i].x  # разница между двумя планетами по х
            dif_y = planets[j].y - planets[i].y  # разница между двумя планетами по y
            d = (dif_x ** 2 + dif_y ** 2) ** 0.5  # пифагорово расстояние между планетами
            f = G * planets[i].m * planets[j].m / d ** 2  # Сила между двумя планетами

            planets[i].f[0] += dif_x * f / d  # обновляем действующие силы на тело
            planets[i].f[1] += dif_y * f / d

            planets[j].f[0] -= dif_x * f / d
            planets[j].f[1] -= dif_y * f / d

            if planets[i].r + planets[j].r > d:  # Находим какие две планеты сталкиваются друг с другом
                collides.append((i, j))

Шестой блок: обрабатываем все столкновения планет

def remove_collides(planets, collides):
    for i in collides:
        p1 = planets[i[0]]
        p2 = planets[i[1]]
        if p1.status and p2.status:  # если обе планеты ещё существуют
            if p1.m > p2.m:  # какая планета больше по массе, та и выиграла
                new_planet = Planet(p1.x, p1.y, p1.r + p2.r, p1.m + p2.m, p1.color)
            else:
                new_planet = Planet(p2.x, p2.y, p1.r + p2.r, p1.m + p2.m, p2.color)

            new_planet.speed = [(p1.m * p1.speed[0] + p2.m * p2.speed[0]) / (p1.m + p2.m),
                                (p1.m * p1.speed[1] + p2.m * p2.speed[1]) / (p1.m + p2.m)]

            planets.append(new_planet)
            p1.status = p2.status = 0

Седьмой блок: для примера, создаём солнце и несколько планет

planets = []

# Солнце
p = Planet(0, 0, 696_000_000, 1.9891 * 10 ** 30, 'orange')
planets.append(p)

for i in range(14, 20):
    p = Planet((-1) ** i * (i * 10 ** 9 + 10 ** 9), 0, 1, 1, 'green')
    planets.append(p)
    planets[i - 13].speed[1] += (-1) ** i * 29273.6 * time_speed / fps

Восьмой блок: запускаем основную программу, обрабатываем все команды, которые могут поступать от пользователя и обновляем массив, в котором содержаться ещё "живые" планеты

tick = 0
tm = time.time()
running = True  # стандартная переменная для запуска цикла 
while running:
    tick += 1
    if tick == 100:  # чтобы fps в левом углу не сходил с ума и не происходило деление на ноль 
        tick = 0
        render_fps = style.render("fps:" + str(int(100 / (time.time() - tm))), True, "blue")
        tm = time.time()

    for event in pygame.event.get():  # начинаем обрабатывать команды
        if event.type == pygame.QUIT:  # выход
            running = False

        if event.type == pygame.MOUSEBUTTONDOWN:  # нажатие любой кнопки мыши
            x = event.pos[0]  # определяем где находится мышка у пользователя
            y = event.pos[1]
            new_x = mouse_x + x * k  # считаем коэффициенты для в нашей "размерности"
            new_y = mouse_y + y * k

            if event.button == 4:  # колёсико мышки вперёд - приближение космической карты
                k *= 0.85
                mouse_x = new_x - x * k
                mouse_y = new_y - y * k

            if event.button == 5:  # колёсико мышки назад - отдаление космической карты
                k /= 0.85
                mouse_x = new_x - x * k
                mouse_y = new_y - y * k

            if event.button == 3:  # правая кнопка мыши - добавляем планету в точку, куда указывает мышка
                planets.append(Planet(new_x, new_y, 6371000, 5.9722 * 10 ** 24, 'blue'))

            if event.button == 2:  # нажатие колёсика - ускорение работы программы
                if time_speed >= 100000:
                    time_speed = 1
                    for i in planets:
                        i.speed[0] /= 100000  # уменьшение скоростей планет
                        i.speed[1] /= 100000
                else:
                    time_speed *= 10
                    for i in planets:
                        i.speed[0] *= 10
                        i.speed[1] *= 10

                render_time_speed = style.render('acceleration: ' + str(time_speed), True, 'blue')  # обновляем отображение ускорения 

        if event.type == pygame.MOUSEMOTION:  # для перемещения по космической карте с помощью удержания левой кнопки мыши
            if pygame.mouse.get_pressed()[0]:
                mouse_x -= event.rel[0] * k
                mouse_y -= event.rel[1] * k

    collides = []  # массив для определения кортежей столкновений 
    update_forces(planets, collides)  # находим все столкновения 

    remove_collides(planets, collides)  # обрабатываем все столкновения

    screen.fill("black")  # обновляем чёрный фон

    new_planets = []
    for planet in planets:  
        if planet.status:  # выбираем только те планеты, которые ещё не столкнулись
            planet.update_coordinates()  # обновляем их значения
            planet.f = [0, 0]  # сбрасываем действующие силы
            new_planets.append(planet)  # сохраняем "живые" планеты
            planet.draw()  # отображаем всё
    planets = new_planets  # обновляем массив

    screen.blit(render_fps, (10, 10))  # выводим fps
    screen.blit(render_time_speed, (10, 50))  # выводим ускорение 
    pygame.display.update()
pygame.quit()  # заканчиваем 

Всё. Это вся программа, которая необходима для данной задачи.

Под конец, давайте полностью смоделируем работу Солнечной системы!

Добавим код для создания Солнца и всех планет

# Земля:
p = Planet(-152 * 10 ** 9, 0, 6371000, 5.9722 * 10 ** 24, 'green')
planets.append(p)
planets[0].speed[1] += 29273.6 * time_speed / fps # скорость вокруг солнца

# Луна:
p = Planet(-363104000 - 152 * 10 ** 9, 0, 1737100, 7.35 * 10 ** 22, 'grey')
planets.append(p)
planets[1].speed[1] = 1023 * time_speed / fps # скорость вокруг земли
planets[1].speed[1] += 29273.6 * time_speed / fps # скорость вокруг солнца

# Меркурий
p = Planet(-69.8 * 10 ** 9, 0, 2439700, 3.33022 * 10 ** 23, "yellow")
planets.append(p)
planets[2].speed[1] += 38000 * time_speed / fps # Скорость вокруг солнца

# Венера
p = Planet(-109 * 10 ** 9, 0, 6051800, 4.8675 * 10 ** 24, "white")
planets.append(p)
planets[3].speed[1] += 34000 * time_speed / fps

# Марс
p = Planet(-249.2 * 10 ** 9, 0, 3390000, 6.4171 * 10 ** 23, "red")
planets.append(p)
planets[4].speed[1] += 23000 * time_speed / fps

# Юпитер
p = Planet(-816.521 * 10 ** 9, 0, 71492000, 1.8986 * 10 ** 27, "brown")
planets.append(p)
planets[5].speed[1] += 12500 * time_speed / fps

# Сатурн
p = Planet(-1.51 * 10 ** 12, 0, 60268000, 5.6846 * 10 ** 26, "beige")
planets.append(p)
planets[6].speed[1] += 9100 * time_speed / fps

# Уран
p = Planet(-3 * 10 ** 12, 0, 	25362000, 8.6813 * 10 ** 25, "pink")
planets.append(p)
planets[7].speed[1] += 6300 * time_speed / fps

# Нептун
p = Planet(-4.5 * 10 ** 12, 0, 24622000, 	1.02409 * 10 ** 26, "blue")
planets.append(p)
planets[8].speed[1] += 4950 * time_speed / fps

# Солнце
p = Planet(0, 0, 696_000_000, 1.9891 * 10 ** 30, 'orange')
planets.append(p)

В связи с тем, что расстояние между планетами очень большое, сначала я решил показать симуляцию движения планет земного типа

Меркурий, Венера, Земля с Луной и Марс. На Марсе новый год ещё не наступил :(
Меркурий, Венера, Земля с Луной и Марс. На Марсе новый год ещё не наступил :(

Теперь немного отдалимся от Солнца и посмотрим на оставшиеся планеты

Это всё, что я хотел рассказать и показать вам сегодня. Смело копируйте программу и проводите свои собственные эксперименты на космической карте!

Tags:
Hubs:
Total votes 5: ↑4 and ↓1+4
Comments31

Articles