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:
- Prototypujesz i zależy Ci na szybkiej konfiguracji
- Twoja scena ma poniżej ~300 ciał sztywnych
- Rozmiar paczki ma znaczenie (PWA, wolne połączenia)
- Potrzebujesz czystej integracji z Three.js bez złożoności ładowania 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);
// 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:
- Potrzebujesz deterministycznego powtórzenia lub synchronizacji multiplayer
- Liczba ciał przekracza ~500
- Potrzebujesz kolizji z terenem (mapa wysokości) lub importowanych kształtów siatki (trimesh)
- Przenikanie szybko poruszających się obiektów jest problemem
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.