Fast Physics hooks for use with react-three-fiber.
Achieved by running the ammo.js physics library in a web-worker. Ammo itself is a WebAssembly wrapper around the powerful Bullet Physics engine. Data is synced with SharedArrayBuffers having minimal impact on the main thread.
r3f-ammo is a modern fork of use-ammojs with added features and compatibility fixes for Next.js, Turbopack, and the modern @react-three/drei ecosystem.
pnpm install r3f-ammoor
bun add r3f-ammo- WebWorker Physics: Heavy calculations run off the main thread
- Soft Body Textures: Added support for textures on soft bodies ✨
- Modern Stats: Integrated with Leva and StatsGl for deep performance profiling
- Next.js Compatible: Fixed bundling issues with Turbopack and Server Components
- Full Soft Body Support: Cloth, ropes, and volumes
Note: Examples below are from the original use-ammojs repo but remain compatible with the r3f-ammo API.
⚠️ Note: CodeSandbox examples do not support SharedArrayBuffers due to missing cross-origin isolation and use regular ArrayBuffers as a fallback. Currently the debug-drawer has no ArrayBuffer fallback implemented and will not render anything.
This package builds upon use-ammojs and use-cannon. While use-cannon is excellent and mature, it lacks Soft Body support and can struggle with large triangle meshes. r3f-ammo brings the full power of Bullet Physics (Ammo.js) to React Three Fiber with modern tooling support.
- âś… Texture support for Soft Bodies (main feature addition)
- âś… Next.js and Turbopack compatibility
- âś… Modern build system using Vite
- âś… Enhanced performance monitoring with Leva integration
- âś… TypeScript improvements
- âś… Updated dependencies and ecosystem compatibility
- Create a Physics World as a React context and simulate it in a web-worker
- Sync three objects to physics Rigid Bodies
- Add Rigid Body support
- Add Soft Body support
- Volumes/Cloth from Triangle Mesh
- Ropes
- Support textures on Soft Bodies ✨ (New in r3f-ammo)
- Deformables
- Add Constraints between Rigid Bodies
- Add Constraints to Soft Bodies (ability to pin nodes in place or to Rigid Bodies)
- Improve Physics API
- Make all props reactive
- Expose more methods through the hook (e.g. setPosition/applyImpulse/more...)
- Support collision callbacks
- Modernize Build System (Vite/Next.js/Turbopack compatibility) ✨
- Automatic refresh rate detection and performance throttling
- Add Raycast queries
- One-time (async) ray-tests
- Continuous queries through a fixed scene component to mitigate worker latency
- Use ArrayBuffers as a fallback for missing cross-origin isolation
- Rigid Bodies
- Soft Bodies
- Debug Rendering
- Simulation management
- Configurable simulation speed
- Expose performance info
- Integrated with @react-three/drei StatsGl component
- Integrated with Leva for graphing
- Automatically pause simulation if tab is out of focus or not rendering (as option)
- Improve automatic shape detection (set shapeType automatically based on the three Mesh type)
- Raycast Vehicle API
- Support for instanced objects
import { Physics, PhysicsStats } from "r3f-ammo";
<Physics drawDebug>
<Scene />
{/* Optional: Add performance visualizer */}
<PhysicsStats showGraph showStats />
</Physics>;Automatically parse Shape parameters from the three Mesh (courtesy of three-to-ammo):
import { Box } from "@react-three/drei";
import { useRigidBody, ShapeType } from "r3f-ammo";
import { Mesh } from "three";
function MyBox() {
// If you need a ref with a narrower type than Object3D, provide a generic argument
const [ref] =
useRigidBody <
Mesh >
(() => ({
mass: 1,
position: [0, 2, 4],
shapeType: ShapeType.BOX,
}));
return (
<Box ref={ref}>
<meshBasicMaterial attach="material" color="red" />
</Box>
);
}Or define Collision Shapes manually:
import { BodyType, ShapeType, ShapeFit } from "r3f-ammo";
import { Vector3 } from "three";
const [playerCapsuleRef] = useRigidBody(() => ({
bodyType: BodyType.DYNAMIC,
shapeType: ShapeType.CAPSULE,
angularFactor: new Vector3(0, 0, 0),
shapeConfig: {
fit: ShapeFit.MANUAL,
halfExtents: new Vector3(0.3, 0.6, 0.3),
},
}));Or add collisions to an imported GLTF scene:
useRigidBody(
() => ({
shapeType: ShapeType.MESH,
bodyType: BodyType.STATIC,
}),
gltf.scene
);import { useSoftBody, SoftBodyType } from "r3f-ammo";
import { Sphere } from "@react-three/drei";
function SoftSphere() {
const [ref] = useSoftBody(() => ({
type: SoftBodyType.TRIMESH,
}));
return (
<Sphere position={[0, 2, 7]} args={[1, 16, 16]} ref={ref}>
<meshPhysicalMaterial attach="material" color="blue" />
</Sphere>
);
}// TODO: Add constraint examplesimport { useAmmo } from "r3f-ammo";
import { Vector3 } from "three";
function RaycastExample() {
const { rayTest } = useAmmo();
const performRaycast = async () => {
const hits = await rayTest({
from: new Vector3(0, 5, 7),
to: new Vector3(0, -1, 7),
multiple: true,
});
if (hits.length) {
console.log(hits[0].object.name, hits[0].hitPosition);
}
};
return <button onClick={performRaycast}>Cast Ray</button>;
}const [playerRef, api] = useRigidBody(() => ({
bodyType: BodyType.DYNAMIC,
shapeType: ShapeType.CAPSULE,
angularFactor: new Vector3(0, 0, 0),
shapeConfig: {
fit: ShapeFit.MANUAL,
halfExtents: new Vector3(0.3, 0.6, 0.3),
},
}));
function handleRespawn() {
// Directly set position/velocity via the API
api.setPosition(new Vector3(0, 0, 0));
api.setLinearVelocity(new Vector3(0, 0, 0));
}Physics Context. Use to wrap all physical objects within the same physics world.
Props:
drawDebug?: boolean- Enable debug rendering
<Physics drawDebug>{/* Your physics objects */}</Physics>Shows a StatsGl panel (FPS/GPU) and registers a Leva monitor graph for Physics CPU timings.
Props:
showGraph?: boolean- Show Leva performance graph (default: true)showStats?: boolean- Show StatsGl panel (default: true)
<PhysicsStats showGraph showStats />Utility functions available anywhere within a <Physics /> context.
const { rayTest } = useAmmo();Create a rigid body physics object.
const [ref, api] = useRigidBody(() => ({
mass: 1,
position: [0, 5, 0],
shapeType: ShapeType.BOX,
}));Returns:
ref: Reference to attach to your Three.js objectapi: Methods to control the body (setPosition, setLinearVelocity, etc.)
Create a soft body physics object (cloth, rope, volume).
const [ref, api] = useSoftBody(() => ({
type: SoftBodyType.TRIMESH,
}));To use SharedArrayBuffers for optimal communication between the main thread and web worker, cross-origin isolation is required in modern browsers.
module.exports = {
async headers() {
return [
{
source: "/(.*)",
headers: [
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" },
],
},
];
},
};import { defineConfig } from "vite";
export default defineConfig({
server: {
headers: {
"Cross-Origin-Embedder-Policy": "require-corp",
"Cross-Origin-Opener-Policy": "same-origin",
},
},
});Click to expand CRA configuration
- Install craco:
npm install @craco/craco --save-dev - Replace
react-scriptswithcracoin yourpackage.jsonscripts - Create
craco.config.jsin project root:
const path = require("path");
module.exports = {
webpack: {
configure: (webpackConfig) => {
// Fix duplicate React instances when using yarn/npm link
webpackConfig.resolve.alias = {
...webpackConfig.resolve.alias,
react: path.resolve("./node_modules/react"),
"@react-three/fiber": path.resolve("./node_modules/@react-three/fiber"),
three: path.resolve("./node_modules/three"),
};
return webpackConfig;
},
},
devServer: {
headers: {
"Cross-Origin-Embedder-Policy": "require-corp",
"Cross-Origin-Opener-Policy": "same-origin",
},
},
};r3f-ammo will automatically fallback to using ArrayBuffers with postMessage() transfers if SharedArrayBuffers are not available. While not as performant as SharedArrayBuffers, this fallback still avoids full data copies on each transfer.
Setting up local development with Next.js/Vite
- Run
npm linkin r3f-ammo root directory - Run
npm link r3f-ammoin your project's directory - In r3f-ammo, run
npm run buildornpm run devto watch for changes - Build and run your project as usual
If using Next.js with npm link, you may need to resolve peer dependency duplications:
// next.config.js
const path = require("path");
module.exports = {
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
react: path.resolve(__dirname, "node_modules/react"),
three: path.resolve(__dirname, "node_modules/three"),
"@react-three/fiber": path.resolve(
__dirname,
"node_modules/@react-three/fiber"
),
};
return config;
},
};r3f-ammo maintains API compatibility with use-ammojs. To migrate:
-
Replace
use-ammojswithr3f-ammoin your package.json -
Update imports:
// Before import { Physics, useRigidBody } from "use-ammojs"; // After import { Physics, useRigidBody } from "r3f-ammo";
-
Update
<PhysicsStats />if used - it now integrates with Leva and StatsGl -
Ensure cross-origin isolation headers are configured for your build tool
- ammo.js - WebAssembly port of Bullet Physics
- three-ammo - Three.js integration foundation
- react-three-fiber - React renderer for Three.js
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
- Original use-ammojs by the Poimandres team
- three-ammo and related work
- The Bullet Physics and Ammo.js communities