Контекст
Для симуляцій, яким потрібна справжня фізика твердих тіл — обробка зіткнень, шарніри, обмеження — потрібен фізичний рушій. Два реалістичні варіанти для браузерних проєктів на Three.js — cannon-es і Rapier.js.
Я використовував обидва для різних симуляцій на цьому сайті. Ось пряме порівняння.
Загальний огляд
| Критерій | cannon-es | Rapier.js |
|---|---|---|
| Мова | TypeScript (JS-форк cannon.js) | Rust → скомпільовано у WASM |
| Розмір бандла | ~150 КБ gzip | ~500 КБ gzip (WASM) |
| Продуктивність | Добре для <500 тіл | Відмінна, масштабується до 10 000+ |
| Детермінізм | Не гарантується | Детермінований на всіх платформах |
| Зручність API | Простий, нативний для JavaScript | Багатослівний (WASM-байндинги) |
| Обгортки для Three.js | cannon-es-debugger, легке налаштування | доступна rapier3d-compat |
| Форми зіткнень | Сфера, куб, циліндр, опукла оболонка | Усе + карта висот, тримеш |
| Неперервне виявлення зіткнень | Немає (можливе «тунелювання») | Так (CCD вбудовано) |
Коли використовувати cannon-es
cannon-es — правильний вибір, коли:
- Ви прототипуєте і хочете швидкого налаштування
- У вашій сцені менше ~300 твердих тіл
- Розмір бандла має значення (PWA, повільні з'єднання)
- Потрібна чиста інтеграція з Three.js без складнощів завантаження WASM
import * as CANNON from 'cannon-es';
const world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0) });
const body = new CANNON.Body({ mass: 1, shape: new CANNON.Sphere(0.5) });
world.addBody(body);
// Синхронізація в циклі анімації:
mesh.position.copy(body.position);
mesh.quaternion.copy(body.quaternion);
Коли використовувати Rapier.js
Rapier — правильний вибір, коли:
- Потрібне детерміноване відтворення або синхронізація в мультиплеєрі
- Кількість тіл перевищує ~500
- Потрібне зіткнення з рельєфом (карта висот) або імпортовані форми меша (тримеш)
- «Тунелювання» швидкорухомих об'єктів є проблемою
Моє практичне правило: Починайте з cannon-es. Якщо ви впираєтесь у його межі (продуктивність, тунелювання, зіткнення з тримешем) — переходьте на Rapier. Концепції переносяться напряму.
Прихована пастка: завантаження WASM
WASM-модуль Rapier потрібно ініціалізувати перед використанням. Це додає асинхронне налаштування, на якому легко спіткнутися:
import RAPIER from '@dimforge/rapier3d-compat';
await RAPIER.init(); // Обов'язково дочекатись перед будь-якими викликами Rapier
const world = new RAPIER.World({ x: 0.0, y: -9.81, z: 0.0 });
Якщо викликати функції Rapier до готовності WASM, ви отримаєте незрозумілі помилки. Обгортання ініціалізації симуляції в асинхронну функцію повністю вирішує цю проблему.