Cannon-es vs Rapier.js — Jak wybrać silnik fizyki dla przeglądarki

Oba działają w przeglądarce; oba obsługują ciała sztywne. Który więc wybrać? Praktyczne porównanie oparte na doświadczeniu z budowy symulacji z każdym z nich.

Kontekst

Dla symulacji, które potrzebują prawdziwej fizyki ciał sztywnych — odpowiedzi na kolizje, przegubów, więzów — potrzebny jest silnik fizyki. Dwie realistyczne opcje dla projektów Three.js działających w przeglądarce to cannon-es i Rapier.js.

Używałem obu do różnych symulacji na tej stronie. Oto bezpośrednie porównanie.

W skrócie

Kryterium cannon-es Rapier.js
Język TypeScript (fork JS-owy cannon.js) Rust → skompilowany do WASM
Rozmiar paczki ~150 KB po gzip ~500 KB po gzip (WASM)
Wydajność Dobra dla <500 ciał Doskonała, skaluje się do 10 000+
Determinizm Nie gwarantowany Deterministyczny na wszystkich platformach
Ergonomia API Proste, natywnie JavaScript Rozwlekłe (wiązania WASM)
Wrappery Three.js cannon-es-debugger, łatwa konfiguracja dostępny rapier3d-compat
Kształty kolizji Sfera, sześcian, walec, otoczka wypukła Wszystkie + mapa wysokości, trimesh
Kolizje ciągłe Brak (możliwe przenikanie) Tak (CCD wbudowane)

Kiedy używać cannon-es

cannon-es jest właściwym wyborem, gdy:

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);
// Synchronizacja w pętli animacji:
mesh.position.copy(body.position);
mesh.quaternion.copy(body.quaternion);

Kiedy używać Rapier.js

Rapier jest właściwym wyborem, gdy:

Moja zasada praktyczna: Zacznij od cannon-es. Jeśli napotkasz jego ograniczenia (wydajność, przenikanie, kolizje trimesh), przejdź na Rapier. Koncepcje przenoszą się bezpośrednio.

Ukryta pułapka: ładowanie WASM

Moduł WASM Rapiera musi zostać zainicjalizowany przed użyciem. To dodaje asynchroniczną konfigurację, która potrafi zaskoczyć:

import RAPIER from '@dimforge/rapier3d-compat';
await RAPIER.init(); // Trzeba poczekać przed jakimkolwiek wywołaniem Rapiera
const world = new RAPIER.World({ x: 0.0, y: -9.81, z: 0.0 });

Jeśli wywołasz funkcje Rapiera zanim WASM będzie gotowy, dostaniesz niejasne błędy. Owinięcie inicjalizacji symulacji w funkcję asynchroniczną rozwiązuje ten problem całkowicie.