mcpField
T
threejs-impl-physics

threejs-impl-physics

github.com/impertio-studio开发23134 次安装30 周前更新
代码仓库

关于 threejs-impl-physics

threejs-impl-physics 是面向 Three.js 开发者的物理实现参考技能,介绍 cannon-es 与 Rapier 的选择、刚体和碰撞器设置及场景同步。

主要功能

  1. 01对比 cannon-es 与 Rapier
  2. 02配置刚体、碰撞器和约束
  3. 03处理物理步进与碰撞事件
  4. 04同步物理状态和 Three.js 网格
  5. 05介绍 React Three Fiber 集成
  6. 06提供射线检测与调试渲染示例

适用场景

  1. 01为 Three.js 场景添加重力与碰撞
  2. 02选择适合场景需求的物理引擎
  3. 03在 React Three Fiber 中配置物理对象

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/impertio-studio/three.js-claude-skill-package --skill threejs-impl-physics

安装后在 Claude Code 中输入/threejs-impl-physics确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

.gitkeep
references/
anti-patterns.md
examples.md
methods.md

详细介绍

threejs-impl-physics

快速参考

引擎选择决策树
标准cannon-esRapier
场景规模< 100 个刚体100–10,000+ 个刚体
是否需要确定性否是(跨平台)
CCD(快速物体)支持有限完全支持
包体积预算~100 KB~300–600 KB(WASM)
初始化同步异步(必须 await init())
原型开发速度更快(API 更简单)更慢(构建器模式)
R3F 集成@react-three/cannon@react-three/rapier

**规则:**对于要求确定性、CCD 或刚体数量 > 100 的生产应用,始终使用 Rapier。原型开发和简单场景使用 cannon-es。

重要警告

绝不要忘记在动画循环中调用 world.step()——否则物理刚体不会移动。

绝不要在 Rapier 中使用 mesh.position.copy(body.position)——Rapier 返回的是普通的 {x, y, z} 对象,而不是 CANNON.Vec3。始终使用 mesh.position.set(pos.x, pos.y, pos.z)。

绝不要在 await RAPIER.init() 完成前使用 Rapier API——WASM 加载完成前,所有 Rapier 类都未定义。

绝不要在动态刚体上使用 Trimesh / trimesh 碰撞体——在这两种引擎中,三角网格都仅支持静态刚体。对于动态凹面几何体,请使用 ConvexPolyhedron / convexHull。

绝不要创建没有对应 Three.js 网格的物理刚体,除非是有意创建不可见碰撞体——孤立的刚体会浪费模拟预算。


cannon-es

世界设置
import * as CANNON from 'cannon-es';

const world = new CANNON.World();
world.gravity.set(0, -9.82, 0);
world.broadphase = new CANNON.SAPBroadphase(world);
world.solver.iterations = 10;
world.allowSleep = true;

始终设置 world.allowSleep = true——休眠刚体会跳过模拟,大幅提升性能。

对于刚体数量 > 20 的场景,始终使用 SAPBroadphase。NaiveBroadphase 的复杂度为 O(n^2)。

刚体类型
类型质量行为
CANNON.Body.DYNAMIC> 0受力和碰撞影响
CANNON.Body.STATIC0不可移动,质量无限
CANNON.Body.KINEMATIC0通过程序移动,并推动动态刚体
const body = new CANNON.Body({
  mass: 5,
  position: new CANNON.Vec3(0, 10, 0),
  shape: new CANNON.Box(new CANNON.Vec3(1, 1, 1)), // half-extents
  linearDamping: 0.01,
  angularDamping: 0.01,
});
world.addBody(body);
形状类型
形状构造函数备注
Boxnew CANNON.Box(halfExtents: Vec3)轴对齐盒体
Spherenew CANNON.Sphere(radius)开销最低的碰撞形状
Cylindernew CANNON.Cylinder(rTop, rBottom, height, segments)圆柱体
Planenew CANNON.Plane()无限地面
ConvexPolyhedronnew CANNON.ConvexPolyhedron({vertices, faces})自定义凸包
Trimeshnew CANNON.Trimesh(vertices, indices)仅支持静态刚体
Heightfieldnew CANNON.Heightfield(data, {elementSize})地形
Particlenew CANNON.Particle()点粒子
材质和接触
const groundMat = new CANNON.Material('ground');
const ballMat = new CANNON.Material('ball');
const contact = new CANNON.ContactMaterial(groundMat, ballMat, {
  friction: 0.4,
  restitution: 0.6,
});
world.addContactMaterial(contact);

// Assign materials to bodies
groundBody.material = groundMat;
ballBody.material = ballMat;

创建 ContactMaterial 后,始终为刚体指定材质——否则 ContactMaterial 不会生效。

约束
约束构造函数用例
PointToPointConstraint(bodyA, pivotA, bodyB, pivotB)球关节
DistanceConstraint(bodyA, bodyB, distance)固定距离杆
HingeConstraint(bodyA, bodyB, {pivotA, axisA, pivotB, axisB})门铰链
LockConstraint(bodyA, bodyB)刚性锁定
ConeTwistConstraint(bodyA, bodyB, options)布娃娃关节
Spring(bodyA, bodyB, options)阻尼弹簧
const hinge = new CANNON.HingeConstraint(bodyA, bodyB, {
  pivotA: new CANNON.Vec3(0, 0, 0),
  axisA: new CANNON.Vec3(0, 1, 0),
  pivotB: new CANNON.Vec3(-2, 0, 0),
  axisB: new CANNON.Vec3(0, 1, 0),
});
world.addConstraint(hinge);
事件
body.addEventListener('collide', (event) => {
  const { contact } = event;
  // contact.ni = contact normal
  // contact.ri = contact point relative to bodyA
  // contact.rj = contact point relative to bodyB
});
body.addEventListener('sleep', () => { /* body went to sleep */ });
body.addEventListener('wakeup', () => { /* body woke up */ });
步进
// ALWAYS use three-argument step for deterministic simulation
const fixedTimeStep = 1 / 60;
const maxSubSteps = 3;

function animate() {
  const delta = clock.getDelta();
  world.step(fixedTimeStep, delta, maxSubSteps);
}

Rapier

WASM 初始化
import RAPIER from '@dimforge/rapier3d-compat';

await RAPIER.init(); // MUST await — ALL APIs undefined before this resolves

const gravity = { x: 0.0, y: -9.81, z: 0.0 };
const world = new RAPIER.World(gravity);

始终在异步函数或顶层 await 中使用 Rapier。在 init() 完成前调用任何 Rapier 构造函数都会抛出运行时错误。

创建 RigidBody(构建器模式)
// Dynamic body
const bodyDesc = RAPIER.RigidBodyDesc.dynamic()
  .setTranslation(0, 10, 0)
  .setLinvel(0, 0, 0)
  .setAngvel({ x: 0, y: 0, z: 0 })
  .setLinearDamping(0.01)
  .setAngularDamping(0.01)
  .setCcdEnabled(true);
const rigidBody = world.createRigidBody(bodyDesc);

// Static body
const staticDesc = RAPIER.RigidBodyDesc.fixed().setTranslation(0, 0, 0);
const staticBody = world.createRigidBody(staticDesc);

// Kinematic (position-based)
const kinDesc = RAPIER.RigidBodyDesc.kinematicPositionBased();
const kinBody = world.createRigidBody(kinDesc);

// Kinematic (velocity-based)
const kinVelDesc = RAPIER.RigidBodyDesc.kinematicVelocityBased();
碰撞体形状
const colliderDesc = RAPIER.ColliderDesc.cuboid(1, 1, 1)
  .setRestitution(0.5)
  .setFriction(0.7);
world.createCollider(colliderDesc, rigidBody);
形状构造函数说明
ColliderDesc.cuboid(hx, hy, hz)长方体半尺寸最常见
ColliderDesc.ball(radius)球体计算开销最低的形状
ColliderDesc.capsule(halfHeight, radius)胶囊体适用于角色
ColliderDesc.cylinder(halfHeight, radius)圆柱体
ColliderDesc.cone(halfHeight, radius)圆锥体
ColliderDesc.convexHull(vertices)凸包来自 Float32Array
ColliderDesc.trimesh(vertices, indices)三角网格仅限静态物体
ColliderDesc.heightfield(nrows, ncols, heights, scale)地形
ColliderDesc.roundCuboid(hx, hy, hz, borderRadius)圆角长方体
射线投射
const ray = new RAPIER.Ray({ x: 0, y: 10, z: 0 }, { x: 0, y: -1, z: 0 });
const hit = world.castRay(ray, 100, true);
if (hit) {
  const hitPoint = ray.pointAt(hit.timeOfImpact);
  const hitCollider = world.getCollider(hit.colliderHandle);
}
CCD(连续碰撞检测)

对于快速移动的物体,务必启用 CCD,以防其穿透薄几何体:

const desc = RAPIER.RigidBodyDesc.dynamic().setCcdEnabled(true);
碰撞事件
const eventQueue = new RAPIER.EventQueue(true);
world.step(eventQueue);

eventQueue.drainCollisionEvents((handle1, handle2, started) => {
  // started === true: collision began
  // started === false: collision ended
});

eventQueue.drainContactForceEvents((event) => {
  const force = event.totalForceMagnitude();
});
调试渲染
const { vertices, colors } = world.debugRender();
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 4));
const material = new THREE.LineBasicMaterial({ vertexColors: true });
const lines = new THREE.LineSegments(geometry, material);
scene.add(lines);

Three.js 同步模式

cannon-es 同步
// Store body-mesh pairs
const pairs = [];

function addPhysicsObject(mesh, body) {
  scene.add(mesh);
  world.addBody(body);
  pairs.push({ mesh, body });
}

function updatePhysics(delta) {
  world.step(1 / 60, delta, 3);
  for (const { mesh, body } of pairs) {
    mesh.position.copy(body.position);       // Vec3 → Vector3 (compatible)
    mesh.quaternion.copy(body.quaternion);    // Quaternion → Quaternion (compatible)
  }
}

cannon-es 的 Vec3 和 Quaternion 与 Three.js 的 .copy() 直接兼容。

Rapier 同步
const pairs = [];

function addPhysicsObject(mesh, rigidBody) {
  scene.add(mesh);
  pairs.push({ mesh, rigidBody });
}

function updatePhysics() {
  world.step();
  for (const { mesh, rigidBody } of pairs) {
    const pos = rigidBody.translation();   // returns {x, y, z}
    const rot = rigidBody.rotation();      // returns {x, y, z, w}
    mesh.position.set(pos.x, pos.y, pos.z);
    mesh.quaternion.set(rot.x, rot.y, rot.z, rot.w);
  }
}

切勿对 Rapier 返回值使用 .copy() —— 它们是普通对象,并非 Three.js 类型。


React Three Fiber 集成

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

function Scene() {
  return (
    <Physics gravity={[0, -9.81, 0]}>
      <RigidBody type="fixed">
        <mesh position={[0, -1, 0]}>
          <boxGeometry args={[20, 1, 20]} />
          <meshStandardMaterial />
        </mesh>
      </RigidBody>
      <RigidBody>
        <mesh position={[0, 5, 0]}>
          <sphereGeometry args={[1]} />
          <meshStandardMaterial />
        </mesh>
      </RigidBody>
    </Physics>
  );
}
@react-three/cannon
import { Physics, useBox, useSphere } from '@react-three/cannon';

function Floor() {
  const [ref] = useBox(() => ({ mass: 0, args: [20, 1, 20], position: [0, -1, 0] }));
  return (
    <mesh ref={ref}>
      <boxGeometry args={[20, 1, 20]} />
      <meshStandardMaterial />
    </mesh>
  );
}

性能比较

特性cannon-esRapier
语言JavaScript编译为 WASM 的 Rust
实时物体数量~1,000~10,000+
包体积~100 KB~300–600 KB
CCD有限支持全面支持
确定性否是(跨平台)
调试渲染手动内置 world.debugRender()
API 风格基于构造函数构建器模式

参考链接

官方来源

评论

支持 Markdown