Skip to content

Repository files navigation

Симуляция трёх тел в AR - 3BP (3 Body Problem)

iOS macOS Swift SwiftUI RealityKit Physics

Иконка приложения

Интерактивная AR-симуляция гравитационных систем в реальном мире

Приложение для iOS и macOS визуализирует сложное взаимодействие небесных тел, используя собственную физическую модель на основе симплектического интегратора Верле по скоростям.

Реализовано в стиле Liquid Glass (iOS 26 HIG) с использованием SwiftUI и RealityKit.

Поддержка macOS

Приложение полностью оптимизировано для работы на macOS (компьютеры с чипами Apple Silicon M1/M2/M3/M4). Оно поддерживает управление мышью/трекпадом и масштабирование окна, сохраняя при этом все физические возможности и визуальные эффекты.

macOS Version

Содержание

Технологический стек

  • Язык: Swift 6 (Strict Concurrency).
  • UI: SwiftUI (Liquid Glass эффекты, адаптивные шторки).
  • Graphics: RealityKit & RealityView (PBR материалы, Point Lights).
  • AR: ARKit (LiDAR Mesh Reconstruction, Raycasting).
  • Physics: Кастомный движок (интегратор Верле по скоростям, $O(N^2)$ оптимизация через симметрию сил).

Архитектура системы

graph TB
    A["📱 ContentView<br/>(SwiftUI UI)"] --> B["🎮 SimulationViewModel<br/>(State Management)"]
    B --> C["⚙️ PhysicsSystem<br/>(Core Physics)"]
    B --> D["🌐 SimulationRenderer<br/>(RealityKit Logic)"]
    C --> E["🔢 Интегратор Верле<br/>(Adaptive Step)"]
    D --> F["🔮 Entities & Materials<br/>(ModelEntity)"]
    D --> G["✨ VFX & Shards<br/>(PointLight/Debris)"]
Loading

Технические ограничения

Самостоятельная реализация физики

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

Средства визуализации и отображения сцены

Для визуализации использованы следующие технологии и средства:

  • RealityKit — основной фреймворк для 3D-графики и AR-сцен
  • RealityView — SwiftUI-представление для 3D-контента (обычный режим)
  • ARView + ARKit — для AR-режима с world tracking, детекцией плоскостей и raycasting
  • ModelEntity & MeshResource — для создания геометрии небесных тел (сферы, дебрис, хвосты)
  • Материалы (SimpleMaterial, PhysicallyBasedMaterial, UnlitMaterial) — для визуальных эффектов
  • PointLight & DirectionalLight — для освещения сцены
  • Entity & AnchorEntity — иерархия объектов сцены

Решение и язык программирования

Решение выполнено на Swift 6.2 в виде универсального 3D/AR приложения для iOS 26, а также доступно как нативное приложение для macOS (чипы Apple Silicon).

Время симуляции

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


Алгоритм расчета

На каждом шаге времени выполняется следующая последовательность действий:

1. Вычисление сил

Для каждого тела вычисляется суммарная сила притяжения со стороны всех остальных тел.

Формула гравитационной силы с мягким ядром:

$$\mathbf{F}_{ij} = G \cdot \frac{m_i m_j}{r_{ij}^2 + \varepsilon} \cdot \hat{\mathbf{r}}_{ij}$$

где:

  • $G = 0.5$ — гравитационная постоянная (нормализованная для визуализации)
  • $m_i, m_j$ — массы взаимодействующих тел
  • $r_{ij}$ — расстояние между телами
  • $\varepsilon = 0.001$ — коэффициент смягчения, предотвращает сингулярность при $r \to 0$
  • $\hat{\mathbf{r}}_{ij}$ — единичный вектор от тела $i$ к телу $j$

Код реализации:

// Вычисление сил для каждого тела (SimulationEngine.swift)
let delta = bodies[j].position - bodies[i].position
let distSq = length_squared(delta)
let dist = sqrt(distSq)
let softening: Float = 0.001
let forceMag = (G * bodies[i].mass * bodies[j].mass) / (distSq + softening)
totalForce += forceMag * (delta / dist)

Почему мягкое ядро? Смягчение предотвращает численные взрывы при очень близких сближениях. На больших расстояниях ($r \gg \varepsilon$) формула соответствует классическому закону $F \propto 1/r^2$, на малых расстояниях — сила занижается для стабильности.


2. Вычисление ускорения

Из полученной суммарной силы для тела определяется его ускорение по второму закону Ньютона:

$$\mathbf{a}_i = \frac{\sum_j \mathbf{F}_{ij}}{m_i}$$

Код реализации:

// После суммирования всех сил
var totalForce: SIMD3<Float> = .zero
for j in 0..<bodies.count {
    if i == j { continue }
    // ... вычисление forceMag и направления ...
    totalForce += forceMag * (delta / dist)
}
accels[i] = totalForce / bodies[i].mass

3. Обновление скорости

Скорость изменяется с учётом полученного ускорения. Используется интегратор Верле по скоростям — симплектический метод второго порядка точности:

$$\mathbf{v}\left(t + \frac{\Delta t}{2}\right) = \mathbf{v}(t) + \frac{1}{2}\mathbf{a}(t) \Delta t$$

$$\mathbf{v}(t + \Delta t) = \mathbf{v}\left(t + \frac{\Delta t}{2}\right) + \frac{1}{2}\mathbf{a}(t + \Delta t) \Delta t$$

Код реализации:

// Первый полушаг скорости
for i in 0..<bodies.count {
    bodies[i].velocity += 0.5 * bodies[i].acceleration * dt
}

// Обновление позиции (шаг 4) и пересчёт ускорения (шаг 2)
// ... (см. ниже)

// Второй полушаг скорости
for i in 0..<bodies.count {
    bodies[i].velocity += 0.5 * bodies[i].acceleration * dt
}

Преимущества интегратора Верле:

  • Симплектический метод → хорошее сохранение энергии
  • Второй порядок точности → низкая численная ошибка
  • Подходит для долгосрочных орбитальных симуляций

4. Обновление положения

Положение тела изменяется с учётом его скорости:

$$\mathbf{x}(t + \Delta t) = \mathbf{x}(t) + \mathbf{v}\left(t + \frac{\Delta t}{2}\right) \Delta t$$

Код реализации:

// После первого полушага скорости
for i in 0..<bodies.count {
    bodies[i].position += bodies[i].velocity * dt
}

5. Перерисовка сцены

Визуализация нового состояния системы выполняется с помощью SimulationRenderer:

Код реализации:

// В цикле RealityKit (ThreeDView.swift или ARContainerView.swift)
renderer.update(root: root, bodies: physicsSystem.bodies, cameraPos: cameraPos, dt: dt, isRunning: true)
renderer.handleCollisions(physicsSystem.collisionEvents, bodies: physicsSystem.bodies, root: root)

Что происходит:

  • Обновляются позиции всех ModelEntity на экране
  • Пересчитываются и визуализируются хвосты траекторий
  • Отображаются текстовые данные (скорость, ускорение, сила)
  • Визуализируются эффекты столкновений (частицы, вспышки, деформация)

Физическая модель

Ньютоновская гравитация с мягким ядром

На малых расстояниях классическая формула гравитации становится численно нестабильной. Поэтому используется смягчённая версия:

$$F_{ij} = \frac{G m_i m_j}{(r_{ij}^2 + \varepsilon)^{1}}$$

Параметры:

  • $G = 0.5$ (нормализовано для визуальной удобности)
  • $\varepsilon = 0.001$ (параметр смягчения)

Что это даёт:

  • На $r = 1.0$: $F \approx 0.499 G m_i m_j$ (практически идентично классическому закону)
  • На $r = 0.1$: $F \approx 0.091 G m_i m_j$ (сила снижается вместо взрыва к бесконечности)
  • Предотвращает "сингулярное поведение" при случайном совпадении позиций

Интегратор Верле по скоростям

Простой метод Эйлера имеет плохое сохранение энергии и быстро расходится на больших временах симуляции: $$\mathbf{x}(t+\Delta t) = \mathbf{x}(t) + \mathbf{v}(t)\Delta t + \frac{1}{2}\mathbf{a}(t)\Delta t^2$$

Интегратор Верле по скоростям (используемый в проекте):

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

При симуляции классической системы "Треугольник Лагранжа" три тела совершают квазиэллиптические орбиты — интегратор Верле обеспечивает правильное поведение траекторий.


Адаптивный временной шаг

Проблема: При фиксированном шаге $\Delta t$ близкие сближения тел приводят к ошибкам.

Решение: Адаптивное дробление шага на подшаги при приближении:

Код реализации:

// Расчёт количества подшагов (SimulationEngine.swift)
private func calculateSubSteps() -> Int {
    for i in 0..<bodies.count {
        for j in i+1..<bodies.count {
            let distSq = length_squared(bodies[j].position - bodies[i].position)
            let combinedRadius = bodies[i].radius + bodies[j].radius
            if distSq < (combinedRadius * 2.0) * (combinedRadius * 2.0) {
                return 8  // Близкое сближение
            }
        }
    }
    return 1  // Обычный режим
}

Логика:

  • Если тела находятся далеко → 1 подшаг
  • Если расстояние между телами меньше двух радиусов → 8 подшагов для точности

Результат: Критические сближения обрабатываются точнее, дальние взаимодействия — быстрее.


Упругие столкновения (жёсткие сферы)

Модель: Когда расстояние между поверхностями тел становится нулевым, применяется импульс для упругого отскока.

Формула импульса: $$j = -\frac{(1+e) \cdot (\mathbf{v}_{rel} \cdot \hat{\mathbf{n}})}{1/m_i + 1/m_j}$$

где $e = 0.7$ — коэффициент реституции (упругости).

$$\Delta \mathbf{v}_i = \frac{j}{m_i} \hat{\mathbf{n}}, \quad \Delta \mathbf{v}_j = -\frac{j}{m_j} \hat{\mathbf{n}}$$

Код реализации:

let normal = normalize(delta)
let relativeVelocity = bodies[i].velocity - bodies[j].velocity
let velocityAlongNormal = dot(relativeVelocity, normal)

if velocityAlongNormal > 0 {
    let e: Float = 0.7  // Коэффициент реституции
    let j_impulse = -(1 + e) * velocityAlongNormal / (1/bodies[i].mass + 1/bodies[j].mass)
    let impulse = j_impulse * normal
    bodies[i].velocity += 1/bodies[i].mass * impulse
    bodies[j].velocity -= 1/bodies[j].mass * impulse
}

Позиционная коррекция (метод Баумгарте):

let slop: Float = 0.001
let correction = max(minDist - dist + slop, 0) * 0.5 * normal
bodies[i].position -= correction
bodies[j].position += correction

Что это даёт:

  • $e = 1.0$ → полностью упругое (энергия сохраняется)
  • $e = 0.7$ → слегка диссипативное (энергия теряется, более реалистично)
  • $e = 0$ → абсолютно неупругое (тела "прилипают")

Радиус тела и визуализация

Эмпирическая связь массы и размера: $$r = m^{1/3} \cdot \text{radiusMultiplier}$$

Код реализации:

var radius: Float {
    pow(mass, 1.0/3.0) * radiusMultiplier
}

Физический смысл: Для сферического тела с фиксированной плотностью $\rho$: $$V = \frac{4}{3}\pi r^3 = \frac{m}{\rho} \Rightarrow r \propto m^{1/3}$$

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


Допущения и приближения

Согласно ТЗ, допускается приближённое решение. В реализации сделаны следующие упрощения:

Допущение Что дало Почему
Нет трения Упрощение логики столкновений Фокус на гравитации и упругих ударах
Нет вращения тел Экономия вычислений Не требуется для 3BP симуляции
Жёсткие сферы Простая геометрия коллизий Достаточно для визуализации
Мягкое ядро $\varepsilon = 0.001$ Численная стабильность Предотвращает взрывы на малых $r$
Адаптивный шаг до 8 подшагов Баланс скорости и точности При близком сближении тел
$G = 0.5$ вместо истинной $G$ Удобная нормализация Масштаб времени соответствует видеокадрам

Реализация

1. Минимальные требования

Требование Статус Описание
Наличие трёх тел Поддерживается произвольное количество тел (N-Body)
Задание параметров Масса, начальное положение, начальная скорость через UI
Функция запуска Play/Pause/Reset кнопки в нижней шторке
Визуализация движения Плавная 3D анимация в реальном времени
Формат 3D RealityKit + RealityView (60 FPS)

Видео 1: Базовая симуляция (3 тела в сценарии "Треугольник Лагранжа")

Базовая симуляция


2. Максимальные требования

Визуализация

Требование Статус Описание
Трёхмерная сцена Полнофункциональная 3D сцена с камерой, освещением
Плавная анимация 60 FPS, синхронизация с интегратором Верле
Траектории движения Динамические хвосты (до 150 точек на тело)
Управление камерой Орбитальная камера (drag для поворота, pinch для зума)
Стабильность Адаптивный временной шаг + интегратор Верле

Видео 2: Траектории и управление камерой

Траектории и камера

Столкновения и эффекты

Требование Статус Описание
Обработка столкновений Упругие удары, импульсы, раздвижение тел
Визуальные эффекты Частицы дебриса, вспышки света (PointLight), Jelly-деформация
Доп. свойства тел Материалы (Matte/Metallic/Glass/Star), размер, цвет

Видео 3: Столкновения и визуальные эффекты

Столкновения

Масштабируемость и оптимизация

Требование Статус Описание
N-body поддержка Поддержка произвольного количества тел
Адаптивный шаг Дробление до 8 подшагов при близких сближениях

Видео 4: N тел ("Фигура-8")

N-body симуляция

Видео 5: AR-режим

AR-режим


Структура проекта

  • TBP/ — Основной код приложения.
    • SimulationEngine.swift — Ядро физической симуляции (PhysicsSystem, CelestialBody, коллизии).
    • SimulationRenderer.swift — Рендеринг RealityKit (ModelEntity, материалы, эффекты).
    • SimulationConfig.swift — Константы физики (G, softening, коэффициенты).
    • SimulationSettings.swift — Настройки симуляции (timeScale, отображение траекторий).
    • ScenarioManager.swift — Готовые сценарии (Лагранж, Фигура-8, Солнечная система).
    • ContentView.swift — UI и управление состоянием (SimulationViewModel).
    • ThreeDView.swift — 3D-вид с орбитальной камерой.
    • ARContainerView.swift — AR-режим с raycasting и коллизиями окружения.
    • BodyInfoAttachment.swift — UI для отображения информации о телах.
    • TBPApp.swift — точка входа приложения.
  • Design/ — Исходники иконок и дизайн-ресурсы.
  • TBPTests/ — Модульные тесты физики.
  • TBPUITests/ — UI-тесты интерфейса.

Проект разработан в соответствии с требованиями HIG (Liquid Glass), с использованием Swift 6.2 Strict Concurrency, SwiftUI и RealityKit для iOS и macOS.

About

3D/AR симуляция гравитации на iOS (RealityKit). Физический движок написан с нуля.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages