r3f-physics by enzed/r3f-skills
npx skills add https://github.com/enzed/r3f-skills --skill r3f-physicsimport { 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>
{/* 下落的盒子 */}
<RigidBody>
<mesh>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
</RigidBody>
{/* 静态地面 */}
<CuboidCollider position={[0, -2, 0]} args={[10, 0.5, 10]} />
</Physics>
</Suspense>
<ambientLight />
<directionalLight position={[5, 5, 5]} />
</Canvas>
)
}
npm install @react-three/rapier
创建物理世界的根组件。
import { Physics } from '@react-three/rapier'
<Canvas>
<Suspense fallback={null}>
<Physics
gravity={[0, -9.81, 0]} // 重力向量
debug={false} // 显示碰撞体线框
timeStep={1/60} // 固定时间步长(或使用 "vary" 表示可变步长)
paused={false} // 暂停模拟
interpolate={true} // 在物理步长之间平滑渲染
colliders="cuboid" // 所有 RigidBody 的默认碰撞体类型
updateLoop="follow" // "follow"(与帧同步)或 "independent"
>
{/* 物理对象 */}
</Physics>
</Suspense>
</Canvas>
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
针对静态场景的性能优化:
<Canvas frameloop="demand">
<Physics updateLoop="independent">
{/* 仅当物体处于活动状态时物理引擎才会触发渲染 */}
</Physics>
</Canvas>
使对象参与物理模拟。
import { RigidBody } from '@react-three/rapier'
// 动态物体(受力和重力影响)
<RigidBody>
<mesh>
<boxGeometry />
<meshStandardMaterial color="red" />
</mesh>
</RigidBody>
// 固定物体(不可移动)
<RigidBody type="fixed">
<mesh>
<boxGeometry args={[10, 0.5, 10]} />
<meshStandardMaterial color="gray" />
</mesh>
</RigidBody>
// 运动学物体(通过程序控制移动)
<RigidBody type="kinematicPosition">
<mesh>
<sphereGeometry />
<meshStandardMaterial color="blue" />
</mesh>
</RigidBody>
| 类型 | 描述 |
|---|---|
dynamic | 受力、重力、碰撞影响(默认) |
fixed | 不可移动,质量无限大 |
kinematicPosition | 通过 setNextKinematicTranslation 移动 |
kinematicVelocity | 通过 setNextKinematicRotation 移动 |
<RigidBody
// 变换
position={[0, 5, 0]}
rotation={[0, Math.PI / 4, 0]}
scale={1}
// 物理属性
type="dynamic"
mass={1}
restitution={0.5} // 弹性系数 (0-1)
friction={0.5} // 表面摩擦力
linearDamping={0} // 减缓线性速度
angularDamping={0} // 减缓角速度
gravityScale={1} // 重力乘数
// 碰撞体生成
colliders="cuboid" // "cuboid" | "ball" | "hull" | "trimesh" | false
// 约束
lockTranslations={false} // 阻止所有平移
lockRotations={false} // 阻止所有旋转
enabledTranslations={[true, true, true]} // 锁定特定轴
enabledRotations={[true, true, true]} // 锁定特定轴
// 休眠
canSleep={true}
ccd={false} // 连续碰撞检测(用于快速移动的物体)
// 命名(用于碰撞事件)
name="player"
/>
RigidBody 自动从子网格生成碰撞体:
// 全局默认设置
<Physics colliders="hull">
<RigidBody>
<Torus /> {/* 获得凸包碰撞体 */}
</RigidBody>
</Physics>
// 每个物体单独覆盖设置
<Physics colliders={false}>
<RigidBody colliders="cuboid">
<Box />
</RigidBody>
<RigidBody colliders="ball">
<Sphere />
</RigidBody>
</Physics>
| 类型 | 描述 | 最适合 |
|---|---|---|
cuboid | 盒子形状 | 盒子、板条箱 |
ball | 球体形状 | 球、球形物体 |
hull | 凸包 | 复杂的凸面形状 |
trimesh | 三角形网格 | 凹面/复杂的静态几何体 |
import {
CuboidCollider,
BallCollider,
CapsuleCollider,
CylinderCollider,
ConeCollider,
HeightfieldCollider,
TrimeshCollider,
ConvexHullCollider
} from '@react-three/rapier'
// 独立碰撞体(静态)
<CuboidCollider position={[0, -2, 0]} args={[10, 0.5, 10]} />
// 在 RigidBody 内部(复合碰撞体)
<RigidBody position={[0, 5, 0]}>
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>
{/* 额外的碰撞体 */}
<BallCollider args={[0.5]} position={[0, 1, 0]} />
<CapsuleCollider args={[0.5, 1]} position={[0, -1, 0]} />
</RigidBody>
// 碰撞体参数参考
<CuboidCollider args={[halfWidth, halfHeight, halfDepth]} />
<BallCollider args={[radius]} />
<CapsuleCollider args={[halfHeight, radius]} />
<CylinderCollider args={[halfHeight, radius]} />
<ConeCollider args={[halfHeight, radius]} />
用于复杂形状:
import { MeshCollider } from '@react-three/rapier'
<RigidBody colliders={false}>
<MeshCollider type="trimesh">
<mesh geometry={complexGeometry}>
<meshStandardMaterial />
</mesh>
</MeshCollider>
</RigidBody>
// 动态物体的凸包碰撞体
<RigidBody colliders={false}>
<MeshCollider type="hull">
<mesh geometry={someGeometry} />
</MeshCollider>
</RigidBody>
import { RigidBody, RapierRigidBody } from '@react-three/rapier'
import { useRef, useEffect } from 'react'
function ForcefulBox() {
const rigidBody = useRef<RapierRigidBody>(null)
useEffect(() => {
if (rigidBody.current) {
// 一次性冲量(瞬时)
rigidBody.current.applyImpulse({ x: 0, y: 10, z: 0 }, true)
// 持续力(每帧施加)
rigidBody.current.addForce({ x: 0, y: 10, z: 0 }, true)
// 扭矩(旋转)
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>
)
}
import { useFrame } from '@react-three/fiber'
function ContinuousForce() {
const rigidBody = useRef<RapierRigidBody>(null)
useFrame(() => {
if (rigidBody.current) {
// 每帧施加力
rigidBody.current.addForce({ x: 0, y: 20, z: 0 }, true)
}
})
return (
<RigidBody ref={rigidBody} gravityScale={0.5}>
<mesh>
<sphereGeometry />
<meshStandardMaterial color="blue" />
</mesh>
</RigidBody>
)
}
import { vec3, quat, euler } from '@react-three/rapier'
function PositionControl() {
const rigidBody = useRef<RapierRigidBody>(null)
const teleport = () => {
if (rigidBody.current) {
// 获取当前变换
const position = vec3(rigidBody.current.translation())
const rotation = quat(rigidBody.current.rotation())
// 设置新变换
rigidBody.current.setTranslation({ x: 0, y: 10, z: 0 }, true)
rigidBody.current.setRotation({ x: 0, y: 0, z: 0, w: 1 }, true)
// 设置速度
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>
)
}
<RigidBody
name="player"
onCollisionEnter={({ manifold, target, other }) => {
console.log('与', other.rigidBodyObject?.name, '发生碰撞')
console.log('接触点', manifold.solverContactPoint(0))
}}
onCollisionExit={({ target, other }) => {
console.log('与', other.rigidBodyObject?.name, '的碰撞结束')
}}
onContactForce={({ totalForce }) => {
console.log('接触力:', totalForce)
}}
onSleep={() => console.log('物体进入休眠')}
onWake={() => console.log('物体唤醒')}
>
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>
</RigidBody>
<CuboidCollider
args={[1, 1, 1]}
onCollisionEnter={(payload) => console.log('碰撞体被击中')}
onCollisionExit={(payload) => console.log('碰撞体离开')}
/>
检测重叠而不产生物理碰撞:
<RigidBody>
{/* 可见的网格 */}
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>
{/* 不可见的传感器触发器 */}
<CuboidCollider
args={[2, 2, 2]}
sensor
onIntersectionEnter={() => console.log('进入触发区域')}
onIntersectionExit={() => console.log('离开触发区域')}
/>
</RigidBody>
// 球门检测示例
<RigidBody type="fixed">
<GoalPosts />
<CuboidCollider
args={[5, 5, 1]}
sensor
onIntersectionEnter={() => console.log('进球!')}
/>
</RigidBody>
控制哪些对象可以碰撞:
import { interactionGroups } from '@react-three/rapier'
// 组 0,与组 0、1、2 交互
<CuboidCollider collisionGroups={interactionGroups(0, [0, 1, 2])} />
// 组 12,与所有组交互
<CuboidCollider collisionGroups={interactionGroups(12)} />
// 组 0 和 5,仅与组 7 交互
<CuboidCollider collisionGroups={interactionGroups([0, 5], 7)} />
// 在 RigidBody 上(应用于所有自动生成的碰撞体)
<RigidBody collisionGroups={interactionGroups(1, [1, 2])}>
<mesh>...</mesh>
</RigidBody>
将刚体连接在一起。
物体之间相对不移动:
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], // 在 bodyA 局部空间中的位置
[0, 0, 0, 1], // 在 bodyA 局部空间中的方向(四元数)
[0, -1, 0], // 在 bodyB 局部空间中的位置
[0, 0, 0, 1], // 在 bodyB 局部空间中的方向
])
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>
</>
)
}
绕一个轴旋转:
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], // 关节在框架局部空间中的位置
[-0.5, 0, 0], // 关节在门局部空间中的位置
[0, 1, 0], // 旋转轴
])
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>
</>
)
}
所有方向上的旋转:
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], // 在 bodyA 局部空间中的位置
[0, 0.5, 0], // 在 bodyB 局部空间中的位置
])
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>
</>
)
}
沿一个轴平移:
import { usePrismaticJoint } from '@react-three/rapier'
function Slider() {
const track = useRef<RapierRigidBody>(null)
const slider = useRef<RapierRigidBody>(null)
usePrismaticJoint(track, slider, [
[0, 0, 0], // 关节在轨道局部空间中的位置
[0, 0, 0], // 关节在滑块局部空间中的位置
[1, 0, 0], // 平移轴
])
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>
</>
)
}
弹性连接:
import { useSpringJoint } from '@react-three/rapier'
function SpringConnection() {
const anchor = useRef<RapierRigidBody>(null)
const ball = useRef<RapierRigidBody>(null)
useSpringJoint(anchor, ball, [
[0, 0, 0], // 在锚点局部空间中的位置
[0, 0, 0], // 在球局部空间中的位置
2, // 静止长度
1000, // 刚度
10, // 阻尼
])
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>
</>
)
}
最大距离约束:
import { useRopeJoint } from '@react-three/rapier'
function RopeConnection() {
const anchor = useRef<RapierRigidBody>(null)
const weight = useRef<RapierRigidBody>(null)
useRopeJoint(anchor, weight, [
[0, 0, 0], // 在锚点局部空间中的位置
[0, 0, 0], // 在重物局部空间中的位置
3, // 最大距离(绳索长度)
])
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>
</>
)
}
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], // 旋转轴
])
useFrame(() => {
if (joint.current) {
// 配置马达:速度,阻尼
joint.current.configureMotorVelocity(10, 2)
}
})
return null
}
为许多相同对象提供高效的物理模拟:
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>
)
}
import { useRapier } from '@react-three/rapier'
import { useEffect } from 'react'
function WorldAccess() {
const { world, rapier } = useRapier()
useEffect(() => {
// 改变重力
world.setGravity({ x: 0, y: -20, z: 0 })
// 遍历所有物体
world.bodies.forEach((body) => {
console.log(body.translation())
})
}, [world])
return null
}
function ManualStep() {
const { step } = useRapier()
const advancePhysics = () => {
step(1 / 60) // 前进一帧
}
return <button onClick={advancePhysics}>步进</button>
}
保存和恢复物理状态:
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}>保存</button>
<button onClick={loadState}>加载</button>
</>
)
}
来自 @react-three/rapier-addons:
import { Attractor } from '@react-three/rapier-addons'
// 吸引附近的物体
<Attractor
position={[0, 0, 0]}
range={10}
strength={5}
type="linear" // "static" | "linear" | "newtonian"
/>
// 排斥物体
<Attractor range={10} strength={-5} position={[5, 0, 0]} />
// 选择性吸引(仅影响特定组)
<Attractor
range={10}
strength={10}
collisionGroups={interactionGroups(0, [2, 3])}
/>
<Physics debug>
{/* 所有碰撞体显示为线框 */}
</Physics>
// 条件调试
<Physics debug={process.env.NODE_ENV === 'development'}>
...
</Physics>
cuboid 和 ball 最快trimesh:改用 hull// 性能优化设置
<Physics
timeStep={1/60}
colliders="cuboid"
gravity={[0, -9.81, 0]}
>
{/* 使用碰撞组来限制检查 */}
<RigidBody collisionGroups={interactionGroups(0, [0, 1])}>
...
</RigidBody>
</Physics>
r3f-fundamentals - R3F 基础知识和钩子r3f-interaction - 用户输入和控制r3f-animation - 将物理与动画结合每周安装量
271
代码仓库
GitHub 星标数
58
首次出现
2026年1月20日
安全审计
安装于
codex204
gemini-cli203
opencode199
github-copilot183
cursor178
claude-code176
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>
)
}
npm install @react-three/rapier
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>
For performance optimization with static scenes:
<Canvas frameloop="demand">
<Physics updateLoop="independent">
{/* Physics only triggers render when bodies are active */}
</Physics>
</Canvas>
Makes objects participate in physics simulation.
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>
| Type | Description |
|---|---|
dynamic | Affected by forces, gravity, collisions (default) |
fixed | Immovable, infinite mass |
kinematicPosition | Moved via setNextKinematicTranslation |
kinematicVelocity | Moved via setNextKinematicRotation |
<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"
/>
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>
| Type | Description | Best For |
|---|---|---|
cuboid | Box shape | Boxes, crates |
ball | Sphere shape | Balls, spherical objects |
hull | Convex hull | Complex convex shapes |
trimesh | Triangle mesh | Concave/complex static geometry |
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]} />
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>
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>
)
}
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>
)
}
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>
)
}
<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>
<CuboidCollider
args={[1, 1, 1]}
onCollisionEnter={(payload) => console.log('Collider hit')}
onCollisionExit={(payload) => console.log('Collider exit')}
/>
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>
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>
Connect rigid bodies together.
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>
</>
)
}
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>
</>
)
}
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>
</>
)
}
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>
</>
)
}
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>
</>
)
}
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>
</>
)
}
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
}
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>
)
}
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
}
function ManualStep() {
const { step } = useRapier()
const advancePhysics = () => {
step(1 / 60) // Advance by one frame
}
return <button onClick={advancePhysics}>Step</button>
}
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>
</>
)
}
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])}
/>
<Physics debug>
{/* All colliders shown as wireframes */}
</Physics>
// Conditional debug
<Physics debug={process.env.NODE_ENV === 'development'}>
...
</Physics>
cuboid and ball are fastesttrimesh for dynamic bodies: Use hull instead// 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>
r3f-fundamentals - R3F basics and hooksr3f-interaction - User input and controlsr3f-animation - Combining physics with animationWeekly Installs
271
Repository
GitHub Stars
58
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex204
gemini-cli203
opencode199
github-copilot183
cursor178
claude-code176
统计分析技能指南:描述性统计、趋势分析与异常值检测方法
808 周安装