Navigate

Search topics across all sections

GitHub
Animation & Physics

Physics with Rapier

Drop a ball on a table and it bounces. Push a box and it slides. Stack blocks and they topple. Rapier turns your 3D scene into a real physics simulation where gravity, collisions, and forces just work. The react-three/rapier library brings the Rapier physics engine to R3F with a declarative API that feels like regular React components.

You build a game where clicking a ball should launch it upward. Instead of using the physics API, you update the ball's position prop with React state. The ball teleports to the new position, clipping through walls and floors because the physics engine never knew it moved.

terminal
RigidBody position changed via React props.
Object teleported from [0, 0, 0] to [0, 5, 0].
Collision detection skipped -- object passed through floor collider.
Use rigidBodyRef.current.applyImpulse() for physics-driven movement.

Real-world

Imagine building a Rube Goldberg machine. You place a ball at the top of a ramp, a domino chain in the middle, and a bucket at the end. You do not animate each piece individually -- you set up the initial conditions and let gravity do the rest.

That is exactly what Rapier does. You describe the shapes, masses, and materials. You tell it about gravity. Then you press play and the physics engine calculates what happens -- 60 times per second, with proper collision detection, friction, and bouncing.

<Physics> is your universe. <RigidBody> is any object that lives in that universe and obeys its laws.

How Rapier Physics Works

Wrap in <Physics>

Creates a Rapier world with gravity

Add <RigidBody>

Each one becomes a physics-simulated object

Auto colliders

Rapier generates collision shapes from your meshes

Simulation runs

Forces, gravity, collisions computed each frame

Meshes sync

Visual positions update to match physics state

Newton's Playground

A physics sandbox. Boxes are stacked on a floor, ready to be knocked over. Use the controls to drop balls from above, adjust gravity strength, and change bounciness. Watch how objects interact, stack, bounce, and topple with real-time physics.

Building It Step by Step

Step 1 -- Set up the physics world

Wrap your scene content in a <Physics> component. This creates the Rapier simulation world. The gravityprop takes a [x, y, z] vector -- Earth gravity is roughly [0, -9.81, 0].

Scene.tsxTSX
import { Physics, RigidBody } from "@react-three/rapier"

function Scene() {
  return (
    <Canvas>
      <Physics gravity={[0, -9.81, 0]}>
        {/* Everything inside here is part of the simulation */}
      </Physics>
    </Canvas>
  )
}

Step 2 -- Add rigid bodies

Wrap any mesh in <RigidBody> to make it a physics object. By default it is dynamic (affected by gravity and forces). Use type="fixed" for immovable objects like floors and walls.

Scene.tsxTSX
<Physics gravity={[0, -9.81, 0]}>
  {/* Dynamic -- falls and bounces */}
  <RigidBody restitution={0.7} friction={0.5}>
    <mesh>
      <sphereGeometry args={[0.5, 32, 32]} />
      <meshStandardMaterial color="tomato" />
    </mesh>
  </RigidBody>

  {/* Fixed -- immovable ground */}
  <RigidBody type="fixed">
    <mesh>
      <boxGeometry args={[10, 0.5, 10]} />
      <meshStandardMaterial color="slategray" />
    </mesh>
  </RigidBody>
</Physics>

restitution controls bounciness (0 = no bounce, 1 = perfect bounce). friction controls sliding resistance.

Step 3 -- Apply forces and impulses

To move physics objects, use the rigid body API. Get a ref to the RigidBody and call methods like applyImpulse (instant push) or applyForce (continuous push).

LaunchBall.tsxTSX
import { useRef } from "react"
import { RigidBody, RapierRigidBody } from "@react-three/rapier"

function LaunchBall() {
  const body = useRef<RapierRigidBody>(null)

  const launch = () => {
    body.current?.applyImpulse(
      { x: 0, y: 8, z: 0 },  // upward push
      true                      // wake up if sleeping
    )
  }

  return (
    <RigidBody ref={body} colliders="ball">
      <mesh onClick={launch}>
        <sphereGeometry args={[0.4]} />
        <meshStandardMaterial color="dodgerblue" />
      </mesh>
    </RigidBody>
  )
}

What you just learned

<Physics> creates a Rapier simulation world with configurable gravity

<RigidBody> makes any mesh a physics object -- dynamic by default, or type='fixed' for static

Colliders are auto-generated from mesh geometry, or you can specify 'ball', 'cuboid', etc.

Move physics objects with applyImpulse or applyForce, never by changing position props

Restitution controls bounciness (0-1+), friction controls sliding resistance

Question

What is the difference between applyImpulse and applyForce? An impulse is an instant push -- like hitting a billiard ball with a cue. A force is continuous -- like wind pushing a sail. Use impulse for one-time events (clicks, jumps) and force for ongoing effects (thrusters, magnets) applied inside useFrame.

Think about it...

You set restitution to 1.0 on both a ball and the floor. You drop the ball from 3 meters. What happens?

Try These Challenges

Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.

Try This!

Beginner

Set gravity to -1 — moon gravity!

Try This!

Beginner

Set bounciness to 1.2 — super bouncy!

Try This!

Beginner

Drop 15 balls at once — chaos!

These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.

1
Moving physics objects with position props
Setting position directly bypasses the physics engine
Don't do this
PhysicsBall.tsxTSX
function Ball({ pos }) {
  // This teleports the object, ignoring physics!
  return (
    <RigidBody position={pos}>
      <mesh>
        <sphereGeometry />
      </mesh>
    </RigidBody>
  )
}
// Updating pos re-renders and teleports
<Ball pos={[newX, newY, newZ]} />
RigidBody position is the initial position. To move physics objects after creation, use applyImpulse, applyForce, setLinvel, or setTranslation on the rigid body ref. Direct position changes bypass collision detection.
2
Forgetting to wrap scene in <Physics>
RigidBody components silently do nothing without a Physics provider
Don't do this
Scene.tsxTSX
<Canvas>
  {/* No <Physics> wrapper! */}
  <RigidBody>
    <mesh>
      <boxGeometry />
    </mesh>
  </RigidBody>
</Canvas>
RigidBody needs a Physics provider as an ancestor. Without it, physics simply does not run. The Physics component initializes the Rapier world, sets up the simulation loop, and manages all rigid bodies within it.
3
No fixed floor for objects to land on
Everything falls through infinity
Don't do this
Scene.tsxTSX
<Physics>
  {/* Balls just fall forever */}
  <RigidBody>
    <mesh>
      <sphereGeometry />
    </mesh>
  </RigidBody>
</Physics>
Without a fixed rigid body acting as the ground, dynamic objects fall indefinitely. Set type='fixed' on floor and wall objects so they have collision shapes but are not affected by gravity.

Best Practices

Use simple colliders when possible

Sphere and cuboid colliders are much faster than trimesh colliders. Use colliders="ball" or colliders="cuboid" instead of the auto-generated mesh shape for better performance.

Set type="fixed" on static objects

Floors, walls, and platforms should be fixed. This tells Rapier they never move, allowing major performance optimizations in collision detection.

Limit active rigid bodies

Each dynamic rigid body adds computational cost. For scenes with many objects, use type="kinematicPosition"for objects you control manually, and keep truly dynamic objects under ~100 for smooth performance.

Use events for game logic

Rapier fires collision events via onCollisionEnterand onCollisionExit on RigidBody. Use these to trigger game logic (scoring, damage, sound effects) instead of checking positions manually.