Cannon-es проти Rapier.js — Вибір фізичного рушія для браузера

Обидва працюють у браузері; обидва обробляють тверді тіла. То який обрати? Практичне порівняння на основі досвіду створення симуляцій з кожним із них.

Контекст

Для симуляцій, яким потрібна справжня фізика твердих тіл — обробка зіткнень, шарніри, обмеження — потрібен фізичний рушій. Два реалістичні варіанти для браузерних проєктів на 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 — правильний вибір, коли:

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 — правильний вибір, коли:

Моє практичне правило: Починайте з 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, ви отримаєте незрозумілі помилки. Обгортання ініціалізації симуляції в асинхронну функцію повністю вирішує цю проблему.