r3f-physics

React Three Fiber physics with Rapier - RigidBody, colliders, forces, joints, sensors. Use when adding physics simulation, collision detection, character controllers, or creating interactive physics-based experiences.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "r3f-physics" with this command: npx skills add enzed/r3f-skills/enzed-r3f-skills-r3f-physics

React Three Fiber Physics (Rapier)

Quick Start

import { Canvas } from '@react-three/fiber'
import { Physics, RigidBody, CuboidCollider } from '@react-three/rapier'
import { Suspense } from 'react'

function Scene() {
  return (
    <Canvas>
      <Suspense fallback={null}>
        <Physics debug>
          {/* Falling box */}
          <RigidBody>
            <mesh>
              <boxGeometry />
              <meshStandardMaterial color="orange" />
            </mesh>
          </RigidBody>

          {/* Static ground */}
          <CuboidCollider position={[0, -2, 0]} args={[10, 0.5, 10]} />
        </Physics>
      </Suspense>

      <ambientLight />
      <directionalLight position={[5, 5, 5]} />
    </Canvas>
  )
}

Installation

npm install @react-three/rapier

Physics Component

The root component that creates the physics world.

import { Physics } from '@react-three/rapier'

<Canvas>
  <Suspense fallback={null}>
    <Physics
      gravity={[0, -9.81, 0]}    // Gravity vector
      debug={false}               // Show collider wireframes
      timeStep={1/60}             // Fixed timestep (or "vary" for variable)
      paused={false}              // Pause simulation
      interpolate={true}          // Smooth rendering between physics steps
      colliders="cuboid"          // Default collider type for all RigidBodies
      updateLoop="follow"         // "follow" (sync with frame) or "independent"
    >
      {/* Physics objects */}
    </Physics>
  </Suspense>
</Canvas>

On-Demand Rendering

For performance optimization with static scenes:

<Canvas frameloop="demand">
  <Physics updateLoop="independent">
    {/* Physics only triggers render when bodies are active */}
  </Physics>
</Canvas>

RigidBody

Makes objects participate in physics simulation.

Basic Usage

import { RigidBody } from '@react-three/rapier'

// Dynamic body (affected by forces/gravity)
<RigidBody>
  <mesh>
    <boxGeometry />
    <meshStandardMaterial color="red" />
  </mesh>
</RigidBody>

// Fixed body (immovable)
<RigidBody type="fixed">
  <mesh>
    <boxGeometry args={[10, 0.5, 10]} />
    <meshStandardMaterial color="gray" />
  </mesh>
</RigidBody>

// Kinematic body (moved programmatically)
<RigidBody type="kinematicPosition">
  <mesh>
    <sphereGeometry />
    <meshStandardMaterial color="blue" />
  </mesh>
</RigidBody>

RigidBody Types

TypeDescription
dynamicAffected by forces, gravity, collisions (default)
fixedImmovable, infinite mass
kinematicPositionMoved via setNextKinematicTranslation
kinematicVelocityMoved via setNextKinematicRotation

RigidBody Properties

<RigidBody
  // Transform
  position={[0, 5, 0]}
  rotation={[0, Math.PI / 4, 0]}
  scale={1}

  // Physics
  type="dynamic"
  mass={1}
  restitution={0.5}           // Bounciness (0-1)
  friction={0.5}              // Surface friction
  linearDamping={0}           // Slows linear velocity
  angularDamping={0}          // Slows angular velocity
  gravityScale={1}            // Multiplier for gravity

  // Collider generation
  colliders="cuboid"          // "cuboid" | "ball" | "hull" | "trimesh" | false

  // Constraints
  lockTranslations={false}    // Prevent all translation
  lockRotations={false}       // Prevent all rotation
  enabledTranslations={[true, true, true]}  // Lock specific axes
  enabledRotations={[true, true, true]}     // Lock specific axes

  // Sleeping
  canSleep={true}
  ccd={false}                 // Continuous collision detection (fast objects)

  // Naming (for collision events)
  name="player"
/>

Colliders

Automatic Colliders

RigidBody auto-generates colliders from child meshes:

// Global default
<Physics colliders="hull">
  <RigidBody>
    <Torus />  {/* Gets hull collider */}
  </RigidBody>
</Physics>

// Per-body override
<Physics colliders={false}>
  <RigidBody colliders="cuboid">
    <Box />
  </RigidBody>

  <RigidBody colliders="ball">
    <Sphere />
  </RigidBody>
</Physics>

Collider Types

TypeDescriptionBest For
cuboidBox shapeBoxes, crates
ballSphere shapeBalls, spherical objects
hullConvex hullComplex convex shapes
trimeshTriangle meshConcave/complex static geometry

Manual Colliders

import {
  CuboidCollider,
  BallCollider,
  CapsuleCollider,
  CylinderCollider,
  ConeCollider,
  HeightfieldCollider,
  TrimeshCollider,
  ConvexHullCollider
} from '@react-three/rapier'

// Standalone collider (static)
<CuboidCollider position={[0, -2, 0]} args={[10, 0.5, 10]} />

// Inside RigidBody (compound collider)
<RigidBody position={[0, 5, 0]}>
  <mesh>
    <boxGeometry />
    <meshStandardMaterial />
  </mesh>

  {/* Additional colliders */}
  <BallCollider args={[0.5]} position={[0, 1, 0]} />
  <CapsuleCollider args={[0.5, 1]} position={[0, -1, 0]} />
</RigidBody>

// Collider args reference
<CuboidCollider args={[halfWidth, halfHeight, halfDepth]} />
<BallCollider args={[radius]} />
<CapsuleCollider args={[halfHeight, radius]} />
<CylinderCollider args={[halfHeight, radius]} />
<ConeCollider args={[halfHeight, radius]} />

Mesh Colliders

For complex shapes:

import { MeshCollider } from '@react-three/rapier'

<RigidBody colliders={false}>
  <MeshCollider type="trimesh">
    <mesh geometry={complexGeometry}>
      <meshStandardMaterial />
    </mesh>
  </MeshCollider>
</RigidBody>

// Convex hull for dynamic bodies
<RigidBody colliders={false}>
  <MeshCollider type="hull">
    <mesh geometry={someGeometry} />
  </MeshCollider>
</RigidBody>

Applying Forces

Using Refs

import { RigidBody, RapierRigidBody } from '@react-three/rapier'
import { useRef, useEffect } from 'react'

function ForcefulBox() {
  const rigidBody = useRef<RapierRigidBody>(null)

  useEffect(() => {
    if (rigidBody.current) {
      // One-time impulse (instantaneous)
      rigidBody.current.applyImpulse({ x: 0, y: 10, z: 0 }, true)

      // Continuous force (apply each frame)
      rigidBody.current.addForce({ x: 0, y: 10, z: 0 }, true)

      // Torque (rotation)
      rigidBody.current.applyTorqueImpulse({ x: 0, y: 5, z: 0 }, true)
      rigidBody.current.addTorque({ x: 0, y: 5, z: 0 }, true)
    }
  }, [])

  return (
    <RigidBody ref={rigidBody}>
      <mesh>
        <boxGeometry />
        <meshStandardMaterial color="red" />
      </mesh>
    </RigidBody>
  )
}

In useFrame

import { useFrame } from '@react-three/fiber'

function ContinuousForce() {
  const rigidBody = useRef<RapierRigidBody>(null)

  useFrame(() => {
    if (rigidBody.current) {
      // Apply force every frame
      rigidBody.current.addForce({ x: 0, y: 20, z: 0 }, true)
    }
  })

  return (
    <RigidBody ref={rigidBody} gravityScale={0.5}>
      <mesh>
        <sphereGeometry />
        <meshStandardMaterial color="blue" />
      </mesh>
    </RigidBody>
  )
}

Getting/Setting Position

import { vec3, quat, euler } from '@react-three/rapier'

function PositionControl() {
  const rigidBody = useRef<RapierRigidBody>(null)

  const teleport = () => {
    if (rigidBody.current) {
      // Get current transform
      const position = vec3(rigidBody.current.translation())
      const rotation = quat(rigidBody.current.rotation())

      // Set new transform
      rigidBody.current.setTranslation({ x: 0, y: 10, z: 0 }, true)
      rigidBody.current.setRotation({ x: 0, y: 0, z: 0, w: 1 }, true)

      // Set velocities
      rigidBody.current.setLinvel({ x: 0, y: 0, z: 0 }, true)
      rigidBody.current.setAngvel({ x: 0, y: 0, z: 0 }, true)
    }
  }

  return (
    <RigidBody ref={rigidBody}>
      <mesh onClick={teleport}>
        <boxGeometry />
        <meshStandardMaterial />
      </mesh>
    </RigidBody>
  )
}

Collision Events

On RigidBody

<RigidBody
  name="player"
  onCollisionEnter={({ manifold, target, other }) => {
    console.log('Collision with', other.rigidBodyObject?.name)
    console.log('Contact point', manifold.solverContactPoint(0))
  }}
  onCollisionExit={({ target, other }) => {
    console.log('Collision ended with', other.rigidBodyObject?.name)
  }}
  onContactForce={({ totalForce }) => {
    console.log('Contact force:', totalForce)
  }}
  onSleep={() => console.log('Body went to sleep')}
  onWake={() => console.log('Body woke up')}
>
  <mesh>
    <boxGeometry />
    <meshStandardMaterial />
  </mesh>
</RigidBody>

On Colliders

<CuboidCollider
  args={[1, 1, 1]}
  onCollisionEnter={(payload) => console.log('Collider hit')}
  onCollisionExit={(payload) => console.log('Collider exit')}
/>

Sensors

Detect overlaps without physical collision:

<RigidBody>
  {/* Visible mesh */}
  <mesh>
    <boxGeometry />
    <meshStandardMaterial />
  </mesh>

  {/* Invisible sensor trigger */}
  <CuboidCollider
    args={[2, 2, 2]}
    sensor
    onIntersectionEnter={() => console.log('Entered trigger zone')}
    onIntersectionExit={() => console.log('Exited trigger zone')}
  />
</RigidBody>

// Goal detection example
<RigidBody type="fixed">
  <GoalPosts />
  <CuboidCollider
    args={[5, 5, 1]}
    sensor
    onIntersectionEnter={() => console.log('Goal!')}
  />
</RigidBody>

Collision Groups

Control which objects can collide:

import { interactionGroups } from '@react-three/rapier'

// Group 0, interacts with groups 0, 1, 2
<CuboidCollider collisionGroups={interactionGroups(0, [0, 1, 2])} />

// Group 12, interacts with all groups
<CuboidCollider collisionGroups={interactionGroups(12)} />

// Groups 0 and 5, only interacts with group 7
<CuboidCollider collisionGroups={interactionGroups([0, 5], 7)} />

// On RigidBody (applies to all auto-generated colliders)
<RigidBody collisionGroups={interactionGroups(1, [1, 2])}>
  <mesh>...</mesh>
</RigidBody>

Joints

Connect rigid bodies together.

Fixed Joint

Bodies don't move relative to each other:

import { useFixedJoint, RapierRigidBody } from '@react-three/rapier'

function FixedJointExample() {
  const bodyA = useRef<RapierRigidBody>(null)
  const bodyB = useRef<RapierRigidBody>(null)

  useFixedJoint(bodyA, bodyB, [
    [0, 0, 0],      // Position in bodyA's local space
    [0, 0, 0, 1],   // Orientation in bodyA's local space (quaternion)
    [0, -1, 0],     // Position in bodyB's local space
    [0, 0, 0, 1],   // Orientation in bodyB's local space
  ])

  return (
    <>
      <RigidBody ref={bodyA} position={[0, 5, 0]}>
        <mesh><boxGeometry /><meshStandardMaterial color="red" /></mesh>
      </RigidBody>
      <RigidBody ref={bodyB} position={[0, 4, 0]}>
        <mesh><boxGeometry /><meshStandardMaterial color="blue" /></mesh>
      </RigidBody>
    </>
  )
}

Revolute Joint (Hinge)

Rotation around one axis:

import { useRevoluteJoint } from '@react-three/rapier'

function HingeDoor() {
  const frame = useRef<RapierRigidBody>(null)
  const door = useRef<RapierRigidBody>(null)

  useRevoluteJoint(frame, door, [
    [0.5, 0, 0],    // Joint position in frame's local space
    [-0.5, 0, 0],   // Joint position in door's local space
    [0, 1, 0],      // Rotation axis
  ])

  return (
    <>
      <RigidBody ref={frame} type="fixed">
        <mesh><boxGeometry args={[0.1, 2, 0.1]} /></mesh>
      </RigidBody>
      <RigidBody ref={door}>
        <mesh><boxGeometry args={[1, 2, 0.1]} /></mesh>
      </RigidBody>
    </>
  )
}

Spherical Joint (Ball-Socket)

Rotation in all directions:

import { useSphericalJoint } from '@react-three/rapier'

function BallJoint() {
  const bodyA = useRef<RapierRigidBody>(null)
  const bodyB = useRef<RapierRigidBody>(null)

  useSphericalJoint(bodyA, bodyB, [
    [0, -0.5, 0],   // Position in bodyA's local space
    [0, 0.5, 0],    // Position in bodyB's local space
  ])

  return (
    <>
      <RigidBody ref={bodyA} type="fixed" position={[0, 3, 0]}>
        <mesh><sphereGeometry args={[0.2]} /></mesh>
      </RigidBody>
      <RigidBody ref={bodyB} position={[0, 2, 0]}>
        <mesh><boxGeometry /></mesh>
      </RigidBody>
    </>
  )
}

Prismatic Joint (Slider)

Translation along one axis:

import { usePrismaticJoint } from '@react-three/rapier'

function Slider() {
  const track = useRef<RapierRigidBody>(null)
  const slider = useRef<RapierRigidBody>(null)

  usePrismaticJoint(track, slider, [
    [0, 0, 0],      // Position in track's local space
    [0, 0, 0],      // Position in slider's local space
    [1, 0, 0],      // Axis of translation
  ])

  return (
    <>
      <RigidBody ref={track} type="fixed">
        <mesh><boxGeometry args={[5, 0.1, 0.1]} /></mesh>
      </RigidBody>
      <RigidBody ref={slider}>
        <mesh><boxGeometry args={[0.5, 0.5, 0.5]} /></mesh>
      </RigidBody>
    </>
  )
}

Spring Joint

Elastic connection:

import { useSpringJoint } from '@react-three/rapier'

function SpringConnection() {
  const anchor = useRef<RapierRigidBody>(null)
  const ball = useRef<RapierRigidBody>(null)

  useSpringJoint(anchor, ball, [
    [0, 0, 0],      // Position in anchor's local space
    [0, 0, 0],      // Position in ball's local space
    2,              // Rest length
    1000,           // Stiffness
    10,             // Damping
  ])

  return (
    <>
      <RigidBody ref={anchor} type="fixed" position={[0, 5, 0]}>
        <mesh><sphereGeometry args={[0.1]} /></mesh>
      </RigidBody>
      <RigidBody ref={ball} position={[0, 3, 0]}>
        <mesh><sphereGeometry args={[0.5]} /></mesh>
      </RigidBody>
    </>
  )
}

Rope Joint

Maximum distance constraint:

import { useRopeJoint } from '@react-three/rapier'

function RopeConnection() {
  const anchor = useRef<RapierRigidBody>(null)
  const weight = useRef<RapierRigidBody>(null)

  useRopeJoint(anchor, weight, [
    [0, 0, 0],      // Position in anchor's local space
    [0, 0, 0],      // Position in weight's local space
    3,              // Max distance (rope length)
  ])

  return (
    <>
      <RigidBody ref={anchor} type="fixed" position={[0, 5, 0]}>
        <mesh><sphereGeometry args={[0.1]} /></mesh>
      </RigidBody>
      <RigidBody ref={weight} position={[0, 2, 0]}>
        <mesh><sphereGeometry args={[0.5]} /></mesh>
      </RigidBody>
    </>
  )
}

Motorized Joints

import { useRevoluteJoint } from '@react-three/rapier'
import { useFrame } from '@react-three/fiber'

function MotorizedWheel({ bodyA, bodyB }) {
  const joint = useRevoluteJoint(bodyA, bodyB, [
    [0, 0, 0],
    [0, 0, 0],
    [0, 0, 1],  // Rotation axis
  ])

  useFrame(() => {
    if (joint.current) {
      // Configure motor: velocity, damping
      joint.current.configureMotorVelocity(10, 2)
    }
  })

  return null
}

Instanced Physics

Efficient physics for many identical objects:

import { InstancedRigidBodies, RapierRigidBody } from '@react-three/rapier'
import { useRef, useMemo } from 'react'

function InstancedBalls() {
  const COUNT = 100
  const rigidBodies = useRef<RapierRigidBody[]>(null)

  const instances = useMemo(() => {
    return Array.from({ length: COUNT }, (_, i) => ({
      key: `ball-${i}`,
      position: [
        (Math.random() - 0.5) * 10,
        Math.random() * 10 + 5,
        (Math.random() - 0.5) * 10,
      ] as [number, number, number],
      rotation: [0, 0, 0] as [number, number, number],
    }))
  }, [])

  return (
    <InstancedRigidBodies
      ref={rigidBodies}
      instances={instances}
      colliders="ball"
    >
      <instancedMesh args={[undefined, undefined, COUNT]}>
        <sphereGeometry args={[0.5]} />
        <meshStandardMaterial color="orange" />
      </instancedMesh>
    </InstancedRigidBodies>
  )
}

Accessing the World

import { useRapier } from '@react-three/rapier'
import { useEffect } from 'react'

function WorldAccess() {
  const { world, rapier } = useRapier()

  useEffect(() => {
    // Change gravity
    world.setGravity({ x: 0, y: -20, z: 0 })

    // Iterate over bodies
    world.bodies.forEach((body) => {
      console.log(body.translation())
    })
  }, [world])

  return null
}

Manual Stepping

function ManualStep() {
  const { step } = useRapier()

  const advancePhysics = () => {
    step(1 / 60)  // Advance by one frame
  }

  return <button onClick={advancePhysics}>Step</button>
}

World Snapshots

Save and restore physics state:

function SnapshotSystem() {
  const { world, setWorld, rapier } = useRapier()
  const snapshot = useRef<Uint8Array>()

  const saveState = () => {
    snapshot.current = world.takeSnapshot()
  }

  const loadState = () => {
    if (snapshot.current) {
      setWorld(rapier.World.restoreSnapshot(snapshot.current))
    }
  }

  return (
    <>
      <button onClick={saveState}>Save</button>
      <button onClick={loadState}>Load</button>
    </>
  )
}

Attractors

From @react-three/rapier-addons:

import { Attractor } from '@react-three/rapier-addons'

// Attract nearby bodies
<Attractor
  position={[0, 0, 0]}
  range={10}
  strength={5}
  type="linear"        // "static" | "linear" | "newtonian"
/>

// Repel bodies
<Attractor range={10} strength={-5} position={[5, 0, 0]} />

// Selective attraction (only affect certain groups)
<Attractor
  range={10}
  strength={10}
  collisionGroups={interactionGroups(0, [2, 3])}
/>

Debug Visualization

<Physics debug>
  {/* All colliders shown as wireframes */}
</Physics>

// Conditional debug
<Physics debug={process.env.NODE_ENV === 'development'}>
  ...
</Physics>

Performance Tips

  1. Use appropriate collider types: cuboid and ball are fastest
  2. Avoid trimesh for dynamic bodies: Use hull instead
  3. Enable sleeping: Bodies at rest stop computing
  4. Use collision groups: Reduce collision checks
  5. Limit active bodies: Too many dynamic bodies hurts performance
  6. Use instanced bodies: For many identical objects
  7. Fixed timestep: More stable than variable
// Performance-optimized setup
<Physics
  timeStep={1/60}
  colliders="cuboid"
  gravity={[0, -9.81, 0]}
>
  {/* Use collision groups to limit checks */}
  <RigidBody collisionGroups={interactionGroups(0, [0, 1])}>
    ...
  </RigidBody>
</Physics>

See Also

  • r3f-fundamentals - R3F basics and hooks
  • r3f-interaction - User input and controls
  • r3f-animation - Combining physics with animation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

r3f-interaction

No summary provided by upstream source.

Repository SourceNeeds Review
1.4K-enzed
General

r3f-fundamentals

No summary provided by upstream source.

Repository SourceNeeds Review
271-enzed
General

r3f-geometry

No summary provided by upstream source.

Repository SourceNeeds Review
248-enzed
General

r3f-shaders

No summary provided by upstream source.

Repository SourceNeeds Review
246-enzed