Приложение полностью оптимизировано для работы на macOS (компьютеры с чипами Apple Silicon M1/M2/M3/M4). Оно поддерживает управление мышью/трекпадом и масштабирование окна, сохраняя при этом все физические возможности и визуальные эффекты.
- Технологический стек
- Архитектура
- Технические ограничения
- Алгоритм расчета
- Физическая модель
- Реализация
- Структура проекта
- Язык: 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)"]
Сама симуляция была реализована самостоятельно без использования готовых физических движков или библиотек с реализованной физикой.
Для визуализации использованы следующие технологии и средства:
- 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).
Симуляция выполняется пошагово во времени с адаптивным временным шагом, который автоматически дробится при критическом сближении тел для обеспечения численной устойчивости.
На каждом шаге времени выполняется следующая последовательность действий:
Для каждого тела вычисляется суммарная сила притяжения со стороны всех остальных тел.
Формула гравитационной силы с мягким ядром:
где:
-
$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)Почему мягкое ядро? Смягчение предотвращает численные взрывы при очень близких сближениях. На больших расстояниях (
Из полученной суммарной силы для тела определяется его ускорение по второму закону Ньютона:
Код реализации:
// После суммирования всех сил
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Скорость изменяется с учётом полученного ускорения. Используется интегратор Верле по скоростям — симплектический метод второго порядка точности:
Код реализации:
// Первый полушаг скорости
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
}Преимущества интегратора Верле:
- Симплектический метод → хорошее сохранение энергии
- Второй порядок точности → низкая численная ошибка
- Подходит для долгосрочных орбитальных симуляций
Положение тела изменяется с учётом его скорости:
Код реализации:
// После первого полушага скорости
for i in 0..<bodies.count {
bodies[i].position += bodies[i].velocity * dt
}Визуализация нового состояния системы выполняется с помощью 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 на экране
- Пересчитываются и визуализируются хвосты траекторий
- Отображаются текстовые данные (скорость, ускорение, сила)
- Визуализируются эффекты столкновений (частицы, вспышки, деформация)
На малых расстояниях классическая формула гравитации становится численно нестабильной. Поэтому используется смягчённая версия:
Параметры:
-
$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$ (сила снижается вместо взрыва к бесконечности) - Предотвращает "сингулярное поведение" при случайном совпадении позиций
Простой метод Эйлера имеет плохое сохранение энергии и быстро расходится на больших временах симуляции:
Интегратор Верле по скоростям (используемый в проекте):
- Энергия сохраняется намного лучше, чем у метода Эйлера
- Траектории остаются стабильными на протяжении длительной симуляции
- Идеален для задач небесной механики
При симуляции классической системы "Треугольник Лагранжа" три тела совершают квазиэллиптические орбиты — интегратор Верле обеспечивает правильное поведение траекторий.
Проблема: При фиксированном шаге
Решение: Адаптивное дробление шага на подшаги при приближении:
Код реализации:
// Расчёт количества подшагов (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 подшагов для точности
Результат: Критические сближения обрабатываются точнее, дальние взаимодействия — быстрее.
Модель: Когда расстояние между поверхностями тел становится нулевым, применяется импульс для упругого отскока.
Формула импульса:
где
Код реализации:
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$ → абсолютно неупругое (тела "прилипают")
Эмпирическая связь массы и размера:
Код реализации:
var radius: Float {
pow(mass, 1.0/3.0) * radiusMultiplier
}Физический смысл: Для сферического тела с фиксированной плотностью
Это обеспечивает реалистичное визуальное представление: более массивные тела выглядят крупнее, но не линейно.
Согласно ТЗ, допускается приближённое решение. В реализации сделаны следующие упрощения:
| Допущение | Что дало | Почему |
|---|---|---|
| Нет трения | Упрощение логики столкновений | Фокус на гравитации и упругих ударах |
| Нет вращения тел | Экономия вычислений | Не требуется для 3BP симуляции |
| Жёсткие сферы | Простая геометрия коллизий | Достаточно для визуализации |
| Мягкое ядро |
Численная стабильность | Предотвращает взрывы на малых |
| Адаптивный шаг до 8 подшагов | Баланс скорости и точности | При близком сближении тел |
|
|
Удобная нормализация | Масштаб времени соответствует видеокадрам |
| Требование | Статус | Описание |
|---|---|---|
| Наличие трёх тел | ✅ | Поддерживается произвольное количество тел (N-Body) |
| Задание параметров | ✅ | Масса, начальное положение, начальная скорость через UI |
| Функция запуска | ✅ | Play/Pause/Reset кнопки в нижней шторке |
| Визуализация движения | ✅ | Плавная 3D анимация в реальном времени |
| Формат 3D | ✅ | RealityKit + RealityView (60 FPS) |
Видео 1: Базовая симуляция (3 тела в сценарии "Треугольник Лагранжа")
| Требование | Статус | Описание |
|---|---|---|
| Трёхмерная сцена | ✅ | Полнофункциональная 3D сцена с камерой, освещением |
| Плавная анимация | ✅ | 60 FPS, синхронизация с интегратором Верле |
| Траектории движения | ✅ | Динамические хвосты (до 150 точек на тело) |
| Управление камерой | ✅ | Орбитальная камера (drag для поворота, pinch для зума) |
| Стабильность | ✅ | Адаптивный временной шаг + интегратор Верле |
Видео 2: Траектории и управление камерой
| Требование | Статус | Описание |
|---|---|---|
| Обработка столкновений | ✅ | Упругие удары, импульсы, раздвижение тел |
| Визуальные эффекты | ✅ | Частицы дебриса, вспышки света (PointLight), Jelly-деформация |
| Доп. свойства тел | ✅ | Материалы (Matte/Metallic/Glass/Star), размер, цвет |
Видео 3: Столкновения и визуальные эффекты
| Требование | Статус | Описание |
|---|---|---|
| N-body поддержка | ✅ | Поддержка произвольного количества тел |
| Адаптивный шаг | ✅ | Дробление до 8 подшагов при близких сближениях |
Видео 4: N тел ("Фигура-8")
Видео 5: 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.





