use-ammojs
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.
yarn add use-ammojs
npm i use-ammojs
Built on top of three-ammo and its related work.
Examples
API Demos
Stress Tests
⚠️ Note that the 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.
Why not use use-cannon instead?
use-cannon is great and a inspiration for this package, but it is missing features like soft-bodies and lacks performance in scenes with large triangle meshes. ammo.js is a direct wrapper around the powerful Bullet Physics engine, which solves these problems.
At the time of writing however use-cannon is more mature and great for most projects.
Roadmap
Main goals:
- <input checked="" disabled="" type="checkbox"> Create a Physics World as a React context and simulate it in a web-worker
- <input checked="" disabled="" type="checkbox"> Sync three objects to physics Rigid Bodies
- <input checked="" disabled="" type="checkbox"> Add Rigid Body support
- <input disabled="" type="checkbox"> Add Soft Body support
- <input checked="" disabled="" type="checkbox"> Volumes/Cloth from Triangle Mesh
- <input checked="" disabled="" type="checkbox"> Ropes
- <input disabled="" type="checkbox"> Support textures on Soft Bodies
- <input disabled="" type="checkbox"> Deformables
- <input disabled="" type="checkbox"> Add Constraints between Rigid Bodies
- <input disabled="" type="checkbox"> Add Constraints to Soft Bodies (ability to pin nodes in place or to Rigid Bodies)
- <input disabled="" type="checkbox"> Improve Physics API
- <input disabled="" type="checkbox"> Make all props reactive
- <input disabled="" type="checkbox"> Expose more methods trough the hook (e.g. setPosition/applyImpulse/more...)
- <input disabled="" type="checkbox"> Support collision callbacks
- <input disabled="" type="checkbox"> Add Examples to the documentation
- <input disabled="" type="checkbox"> Set up Benchmarks to compare cannon, ammo with ArrayBuffers and ammo with SharedArrayBuffers
Low priority goals (for unchecked tasks):
- <input disabled="" type="checkbox"> Automatic refresh rate detection and performance throttling (i.e. match the simulation rate to the requestAnimationFrame-rate and throttle performance if simulation steps take too long)
- <input disabled="" type="checkbox"> Add Raycast queries
- <input checked="" disabled="" type="checkbox"> One-time (async) ray-tests
- <input disabled="" type="checkbox"> Continuous queries trough a fixed scene component to mitigate worker latency (TODO: check if necessary)
- <input checked="" disabled="" type="checkbox"> Use ArrayBuffers as a fallback for missing cross-origin isolation
- <input checked="" disabled="" type="checkbox"> Rigid Bodies
- <input checked="" disabled="" type="checkbox"> Soft Bodies
- <input disabled="" type="checkbox"> Debug Rendering
- <input checked="" disabled="" type="checkbox"> Simulation managment
- <input checked="" disabled="" type="checkbox"> Configurable simulation speed
- <input checked="" disabled="" type="checkbox"> Expose performance info
- <input disabled="" type="checkbox"> Integrate to @react-three/drei Stats component
- <input disabled="" type="checkbox"> Automatically pause simulation if tab is out of focus or not rendering (as option)
- <input disabled="" type="checkbox"> Improve the automatic shape detection (set shapeType automatically based on the three Mesh type)
- <input disabled="" type="checkbox"> Raycast Vehicle API
- <input disabled="" type="checkbox"> Support for instanced objects
- <input disabled="" type="checkbox"> Support and document manual bundling of the wasm file
- Currently the wasm library is inlined with a base64 string for ease of use. Users who want to save a few bytes can serve it as a seperate file with the
application/wasm
Content-Type in their own deployment. There should be a bundle available without the inlined wasm for that use-case.
- Currently the wasm library is inlined with a base64 string for ease of use. Users who want to save a few bytes can serve it as a seperate file with the
Quick Start
1. Wrap your scene in a Physics Provider
import { Physics } from "use-ammojs";
<Physics drawDebug>[...]</Physics>;
2.a Make objects physical (Rigid Bodies)
Automatically parse Shape parameters from the three Mesh (courtesy of three-to-ammo):
import { Box } from "@react-three/drei";
import { useRigidBody, ShapeType } from "use-ammojs";
function MyBox() {
const [ref] = useRigidBody(() => ({
mass: 1,
position: [0, 2, 4],
shapeType: ShapeType.BOX,
}));
return (
<Box ref={ref}>
<meshBasicMaterial attach="material" color="red" />
</Box>
);
}
or define Collision Shapes manually:
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
);
2.a Make objects squishy (Soft Bodies)
const [ref] = useSoftBody(() => ({
type: SoftBodyType.TRIMESH,
}));
return (
<Sphere position={[0, 2, 7]} args={[1, 16, 16]} ref={ref}>
<meshPhysicalMaterial attach="material" color="blue" />
</Sphere>
);
2.c Add Constraints
TODO;
3.a Raycasts
const { rayTest } = useAmmo();
[...]
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)
}
3.b Update Motion State
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() {
api.setPosition(new Vector3(0, 0, 0));
api.setLinearVelocity(new Vector3(0, 0, 0));
}
Documentation
Components
<Physics />
Phyiscs Context. Use to wrap all physical objects within the same physics world.
<PhysicsStats />
Shows a stats.js panel with physics timing info. Use within a <Physics />
Context
Hooks
const { rayTest } = useAmmo();
Utility funcionts available anywhere in the <Physics />
context.
const [ref, api] = useRigidBody();
const [ref, api] = useSoftBody();
Cross-origin isolation
To use SharedArrayBuffers
for better communication between the main-thread and the web-worker-thread, a cross-origin isolated environment is necessary in modern browsers.
This requires sending the following HTTP headers in the response of the main html document (Learn more):
`
http request
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
use-ammojs will fallback to using `ArrayBuffers` and `postMessage()` transfers if `SharedArrayBuffers` are not available. This is not as bad as a full copy on each transfer, but it does not allow the data to be availble on both threads at the same time.
### Developing locally using use-ammojs
<details>
<summary> Setting up react-scripts to work with yarn link using @craco/craco </summary>
1. `yarn add @craco/craco --dev`
2. Replace `react-scripts` with `craco` in your `package.json` (see [@craco/craco](https://www.npmjs.com/package/@craco/craco) documentation)
3. Add `craco.config.js` to project root:
```js
const path = require("path");
module.exports = {
webpack: {
configure: (webpackConfig) => {
// Fix that prevents a duplicate react library being imported when using a linked yarn package
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;
},
},
// Make sure SharedArrayBuffers are available locally
devServer: {
headers: {
"Cross-Origin-Embedder-Policy": "require-corp",
"Cross-Origin-Opener-Policy": "same-origin",
},
},
};
- Run
yarn link
in use-ammojs root directory - Run
yarn link use-ammojs
in your project's directory - Run
yarn start
in use-ammojs to start the development bundler - Build and run your project as usual