Physics in r3f using the useCannon hook
Scenes in WebGL really come alive when they're moving and interactive. Creating realistic movement is hard though, so it's often good to approximate the same calculations that happen in reality instead of trying to write code to move things around. This calls for a physics engine.
There are a few Javascript physics engines available - notably ammo.js, cannon.js, and box2d.js, but plenty of others are around if you search hard enough. Which one you should pick really depends on what you want to simulate. As a rule 2D physics is much, much faster than 3D, so if you only need things to bounce around or fall over in 2 axises then pick something that does 2D.
Something to note here is that the physics engine is quite separate to both react-three-fiber and Three.JS. r3f and Three.JS only draw things and enable events like user input. A physics engine simulates how things move and rotate. In a physics enabled Three.JS scene the position and rotation of each object is updated automatically in each frame by the physics engine, but it's up to you to write the code to make those updates happen.
There is an exception though. A React hook for the cannon.js engine has been written by the amazing team at Poimandres, and it does almost all of the work for us. All we need to do is wrap the <physics>
component around things we want to move, use some physics hooks in our components, and the useCannon hook does the rest.
useCannon
useCannon does some really clever things behind the scenes. It uses a webworker to offload the computation work to a separate thread, meaning the browser should remain responsive even if there's a lot going on in the scene. It enables instanced physics so scenes that use instanced geometry can have simulated physics across lots of objects. And lastly, it gives us the power to subscribe to physics properties like the position of something to let us react to how things are changing in the scene.
Dropping the ball
To use useCannon in a scene the first thing to do is to import the Physics component.
import { Physics } from "@react-three/cannon";
This is a wrapper that goes around all the components that are going to have physics applied to them. Physics needs to be wrapped in a Suspense component in order to work.
const App = () => {
return (
<Canvas
style={{ height: 600, width: 600 }}
camera={{ position: [0, 10, 10] }}
>
<pointLight position={[5, 5, 5]} />
<Suspense fallback={null}>
<Physics>
[... THINGS WITH PHYSICS GO HERE]
</Physics>
</Suspense>
<OrbitControls />
<Stats />
</Canvas>
);
};
Adding physics to a component is incredibly straightforward with useCannon. For example, to implement a 'ground' plane we can use the usePlane
hook, and give it some properties like it's rotation and size (it's 1x1 by default).
export default function Ground(props) {
const [ref] = usePlane(() => ({ ...props }));
return (
<mesh ref={ref} castShadow receiveShadow>
<planeGeometry attach="geometry" args={[100, 100]} />
<meshStandardMaterial color={"#dddddd"} />
</mesh>
);
}
To add physics to a sphere we can use the useSphere hook. This is a little more complex than the ground because the sphere's need to know how much mass they have in order for the physics engine to simulate the effect of gravity. In the Ball component's useSphere hook we need to add a mass property.
useSphere(() => ({ mass: 1, position: position }));
That's literally all there is to it. Now the useCannon physics engine will update every Ball and simulate the way they interact with each other and with the ground.
useCannon can do a lot more than simple gravity. It can simulate bounciness, damping, different sorts of contacts, constraints like hinges and springs, and much more. It really makes a scene come to life.