Trending-skills webphysics-avbd-engine
WebGPU rigid-body/soft-body physics engine based on the AVBD (Augmented Vertex Block Descent) solver
install
source · Clone the upstream repo
git clone https://github.com/Aradotso/trending-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Aradotso/trending-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/webphysics-avbd-engine" ~/.claude/skills/aradotso-trending-skills-webphysics-avbd-engine && rm -rf "$T"
manifest:
skills/webphysics-avbd-engine/SKILL.mdsource content
webphysics-avbd-engine
Skill by ara.so — Daily 2026 Skills collection.
What It Does
webphysics is an experimental WebGPU-accelerated rigid-body and soft-body physics engine implementing the AVBD (Augmented Vertex Block Descent) solver from Giles et al. (2025). It runs entirely on the GPU using WebGPU compute shaders and supports:
- Rigid-body simulation with contacts, friction, and joints
- GPU broad-phase collision detection via LBVH (Linear BVH)
- Narrow-phase manifold generation with warm-start persistence
- Graph-coloring-based parallel body solves
- Springs and soft-body constraints
- Body sleeping/diagnostics
Browser support: Chrome only (requires WebGPU). This is an experimental proof-of-concept, not a production library.
Installation & Setup
git clone https://github.com/jure/webphysics.git cd webphysics npm install npm run dev # development server npm run build # production build
The dev server typically starts at
http://localhost:5173 (Vite-based).
Project Structure
src/ ├── physics/ │ ├── PhysicsEngine.ts # Main orchestration: substep loop, init, step │ └── gpu/ │ ├── avbdState.ts # Primal/dual solve, coloring, velocity finalization │ ├── broadPhase.ts # LBVH broad-phase candidate generation │ ├── contactGeneration.ts # Narrow-phase manifolds, per-body constraint lists │ ├── contactRecord.ts # Warm-start state persistence │ └── avbdState.ts # Inertial targets, primal init, iteration ├── lvbh/ │ └── GPULBVHBuilder.ts # GPU LBVH construction └── ...
Core API Usage
Initializing the Physics Engine
import { PhysicsEngine } from './src/physics/PhysicsEngine'; // Requires an existing GPUDevice const adapter = await navigator.gpu.requestAdapter(); const device = await adapter.requestDevice(); const engine = new PhysicsEngine(device); await engine.init();
Adding Rigid Bodies
// Add a static ground plane engine.addBody({ type: 'box', position: [0, -1, 0], rotation: [0, 0, 0, 1], // quaternion [x, y, z, w] halfExtents: [10, 0.5, 10], mass: 0, // 0 = static/infinite mass restitution: 0.3, friction: 0.5, }); // Add a dynamic rigid box engine.addBody({ type: 'box', position: [0, 5, 0], rotation: [0, 0, 0, 1], halfExtents: [0.5, 0.5, 0.5], mass: 1.0, restitution: 0.2, friction: 0.6, });
Stepping the Simulation
const TIMESTEP = 1 / 60; const SUBSTEPS = 10; function gameLoop(dt: number) { engine.step(dt, SUBSTEPS); // Read back positions for rendering const bodyStates = engine.getBodyStates(); renderBodies(bodyStates); requestAnimationFrame(gameLoop); } requestAnimationFrame(gameLoop);
Reading Body State for Rendering
// After engine.step(), retrieve updated transforms const states = engine.getBodyStates(); for (const state of states) { const { position, rotation, bodyIndex } = state; // position: [x, y, z] // rotation: quaternion [x, y, z, w] updateMeshTransform(bodyIndex, position, rotation); }
Adding Joints / Constraints
// Distance joint between two bodies engine.addJoint({ type: 'distance', bodyA: 0, bodyB: 1, anchorA: [0, 0.5, 0], // local-space anchor on body A anchorB: [0, -0.5, 0], // local-space anchor on body B restLength: 1.0, stiffness: 1e4, });
Adding Springs (Soft Bodies)
engine.addSpring({ bodyA: 2, bodyB: 3, anchorA: [0, 0, 0], anchorB: [0, 0, 0], restLength: 0.8, stiffness: 500, damping: 10, });
AVBD Pipeline Reference
The solver follows Algorithm 1 from the AVBD paper:
1. collision detection (x^t) ↓ 2. broad phase (LBVH) → src/lvbh/GPULBVHBuilder.ts ↓ 3. narrow phase + warm start → src/physics/gpu/contactGeneration.ts ↓ 4. per-body constraint lists → src/physics/gpu/avbdState.ts ↓ 5. graph coloring → src/physics/gpu/avbdState.ts ↓ 6. inertial target y, primal init, warm-start α/γ ↓ 7. [loop] colored primal body solve (approx Hessian) ↓ 8. [loop] dual + stiffness update ↓ 9. finalize velocities
Key files per stage:
| Stage | File |
|---|---|
| Orchestration | |
| Broad phase | |
| Narrow phase | |
| Contact records | |
| AVBD solve | |
| LBVH builder | |
Configuration Patterns
Solver Parameters
// Passed during engine construction or step engine.step(dt, substeps, { gravity: [0, -9.81, 0], iterations: 10, // AVBD inner iterations per substep restitutionThreshold: 1.0, });
Tuning Stability
- Increase
(e.g., 20) for stiff stacks or fast-moving bodiessubsteps - Increase
for better constraint convergenceiterations - Use
for static bodies (never moves, acts as infinite mass)mass: 0 - Lower
values for softer, more stable jointsstiffness - Set
+ highrestitution: 0
for non-bouncy stackingfriction
Common Patterns
Stack of Boxes
const groundIndex = engine.addBody({ type: 'box', position: [0, 0, 0], halfExtents: [5, 0.25, 5], mass: 0, friction: 0.7, restitution: 0.1, }); for (let i = 0; i < 8; i++) { engine.addBody({ type: 'box', position: [0, 0.5 + i * 1.05, 0], halfExtents: [0.5, 0.5, 0.5], mass: 1.0, friction: 0.5, restitution: 0.1, }); }
Pendulum Chain with Distance Joints
let prevIndex = engine.addBody({ type: 'box', position: [0, 5, 0], halfExtents: [0.1, 0.1, 0.1], mass: 0, friction: 0, restitution: 0, }); for (let i = 1; i <= 5; i++) { const curr = engine.addBody({ type: 'box', position: [0, 5 - i, 0], halfExtents: [0.15, 0.15, 0.15], mass: 1.0, friction: 0.1, restitution: 0, }); engine.addJoint({ type: 'distance', bodyA: prevIndex, bodyB: curr, anchorA: [0, -0.15, 0], anchorB: [0, 0.15, 0], restLength: 0.7, stiffness: 1e5, }); prevIndex = curr; }
Integrate with Three.js Rendering
import * as THREE from 'three'; const meshes: THREE.Mesh[] = []; function syncPhysicsToRender() { const states = engine.getBodyStates(); states.forEach((state, i) => { if (!meshes[i]) return; meshes[i].position.set(...state.position); meshes[i].quaternion.set( state.rotation[0], state.rotation[1], state.rotation[2], state.rotation[3] ); }); } function animate() { engine.step(1 / 60, 10); syncPhysicsToRender(); renderer.render(scene, camera); requestAnimationFrame(animate); }
Troubleshooting
WebGPU Not Available
Error: navigator.gpu is undefined
- Only Chrome 113+ supports WebGPU by default
- Enable via
on older versionschrome://flags/#enable-unsafe-webgpu - Firefox/Safari do not currently support WebGPU
Simulation Explodes / Bodies Flying Off
- Reduce timestep or increase
substeps - Lower joint
valuesstiffness - Ensure static bodies have
mass: 0 - Check that
are positive and non-zerohalfExtents
Bodies Sinking Through Ground
- Increase
(try 15–20)iterations - Increase
substeps - Check collision shape sizing matches visual mesh
Performance Issues
- This is a Chrome-only WebGPU project; GPU driver issues can cause slowdowns
- Reduce body count or iteration count
- Check
to ensure hardware acceleration is activechrome://gpu
Build Errors
# Ensure Node.js >= 18 node --version # Clear cache rm -rf node_modules dist npm install npm run build
Limitations & Roadmap Notes
- Chrome only — no Firefox/Safari support yet
- Not a drop-in npm package; must clone and integrate manually
- Double-buffered position updates (for same-color conflict safety) not yet implemented — current path uses in-place colored body solve in
avbdState.ts - Experimental API — breaking changes expected
- No TypeScript type declarations exported for external use yet