Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
openaec-foundation avatar

Threejs Impl Physics

  • 18 installs
  • 11 repo stars
  • Updated July 8, 2026
  • openaec-foundation/three.js-claude-skill-package

Helps with ai & agent building tasks.

About

threejs-impl-physics is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.

  • threejs-impl-physics
  • AI & Agent Building
  • AI-coding skill

Threejs Impl Physics by the numbers

  • 18 all-time installs (skills.sh)
  • +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
  • Ranked #10,719 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-impl-physics

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs18
repo stars11
Last updatedJuly 8, 2026
Repositoryopenaec-foundation/three.js-claude-skill-package

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

threejs-impl-physics

Quick Reference

Engine Selection Decision Tree

Criterioncannon-esRapier
Scene size< 100 bodies100–10,000+ bodies
Determinism neededNoYes (cross-platform)
CCD (fast objects)LimitedFull support
Bundle size budget~100 KB~300–600 KB (WASM)
InitializationSynchronousAsync (MUST await init())
Prototyping speedFaster (simpler API)Slower (builder pattern)
R3F integration@react-three/cannon@react-three/rapier

Rule: ALWAYS use Rapier for production applications requiring determinism, CCD, or > 100 bodies. Use cannon-es for prototyping and simple scenes.

Critical Warnings

NEVER forget to call world.step() in the animation loop — physics bodies will NOT move without it.

NEVER use mesh.position.copy(body.position) with Rapier — Rapier returns plain {x, y, z} objects, NOT CANNON.Vec3. ALWAYS use mesh.position.set(pos.x, pos.y, pos.z).

NEVER use Rapier APIs before await RAPIER.init() completes — ALL Rapier classes are undefined until WASM loads.

NEVER use Trimesh / trimesh colliders on dynamic bodies — triangle meshes are STATIC only in both engines. Use ConvexPolyhedron / convexHull for dynamic concave geometry.

NEVER create physics bodies without corresponding Three.js meshes unless intentionally creating invisible colliders — orphaned bodies waste simulation budget.

---

cannon-es

World Setup

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;

ALWAYS set world.allowSleep = true — sleeping bodies skip simulation and dramatically improve performance.

ALWAYS use SAPBroadphase for scenes with > 20 bodies. NaiveBroadphase is O(n^2).

Body Types

TypeMassBehavior
CANNON.Body.DYNAMIC> 0Affected by forces and collisions
CANNON.Body.STATIC0Immovable, infinite mass
CANNON.Body.KINEMATIC0Moved programmatically, pushes dynamic bodies
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);

Shape Types

ShapeConstructorNotes
Boxnew CANNON.Box(halfExtents: Vec3)Axis-aligned box
Spherenew CANNON.Sphere(radius)Cheapest collision shape
Cylindernew CANNON.Cylinder(rTop, rBottom, height, segments)Cylinder
Planenew CANNON.Plane()Infinite ground plane
ConvexPolyhedronnew CANNON.ConvexPolyhedron({vertices, faces})Custom convex hull
Trimeshnew CANNON.Trimesh(vertices, indices)STATIC only
Heightfieldnew CANNON.Heightfield(data, {elementSize})Terrain
Particlenew CANNON.Particle()Point particle

Materials and Contacts

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;

ALWAYS assign materials to bodies after creating ContactMaterial — without assignment, the ContactMaterial has NO effect.

Constraints

ConstraintConstructorUse Case
PointToPointConstraint(bodyA, pivotA, bodyB, pivotB)Ball joint
DistanceConstraint(bodyA, bodyB, distance)Fixed distance rod
HingeConstraint(bodyA, bodyB, {pivotA, axisA, pivotB, axisB})Door hinge
LockConstraint(bodyA, bodyB)Rigid lock
ConeTwistConstraint(bodyA, bodyB, options)Ragdoll joints
Spring(bodyA, bodyB, options)Damped spring
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);

Events

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 */ });

Stepping

// 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 Initialization

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);

ALWAYS wrap Rapier usage in an async function or top-level await. Calling ANY Rapier constructor before init() resolves throws a runtime error.

RigidBody Creation (Builder Pattern)

// 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();

Collider Shapes

const colliderDesc = RAPIER.ColliderDesc.cuboid(1, 1, 1)
  .setRestitution(0.5)
  .setFriction(0.7);
world.createCollider(colliderDesc, rigidBody);
ShapeConstructorNotes
ColliderDesc.cuboid(hx, hy, hz)Box half-extentsMost common
ColliderDesc.ball(radius)SphereCheapest shape
ColliderDesc.capsule(halfHeight, radius)CapsuleGood for characters
ColliderDesc.cylinder(halfHeight, radius)Cylinder
ColliderDesc.cone(halfHeight, radius)Cone
ColliderDesc.convexHull(vertices)Convex hullFrom Float32Array
ColliderDesc.trimesh(vertices, indices)Triangle meshSTATIC only
ColliderDesc.heightfield(nrows, ncols, heights, scale)Terrain
ColliderDesc.roundCuboid(hx, hy, hz, borderRadius)Rounded box

Ray Casting

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 (Continuous Collision Detection)

ALWAYS enable CCD on fast-moving bodies to prevent tunneling through thin geometry:

const desc = RAPIER.RigidBodyDesc.dynamic().setCcdEnabled(true);

Collision Events

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();
});

Debug Rendering

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 Sync Pattern

cannon-es Sync

// 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 and Quaternion are directly compatible with Three.js .copy().

Rapier Sync

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);
  }
}

NEVER use .copy() with Rapier return values — they are plain objects, NOT Three.js types.

---

React Three Fiber Integration

@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>
  );
}

---

Performance Comparison

Featurecannon-esRapier
LanguageJavaScriptRust compiled to WASM
Real-time bodies~1,000~10,000+
Bundle size~100 KB~300–600 KB
CCDLimitedFull support
DeterministicNoYes (cross-platform)
Debug renderingManualBuilt-in world.debugRender()
API styleConstructor-basedBuilder pattern

---

Reference Links

  • references/methods.md — API signatures for cannon-es and Rapier
  • references/examples.md — Working integration examples
  • references/anti-patterns.md — Common physics mistakes and fixes

Official Sources

  • https://pmndrs.github.io/cannon-es/docs/
  • https://rapier.rs/docs/user_guides/javascript/getting_started
  • https://github.com/pmndrs/react-three-rapier
  • https://github.com/pmndrs/use-cannon

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.