webphysics-avbd-engine
Installation
SKILL.md
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 | src/physics/PhysicsEngine.ts |
| Broad phase | src/physics/gpu/broadPhase.ts |
| Narrow phase | src/physics/gpu/contactGeneration.ts |
| Contact records | src/physics/gpu/contactRecord.ts |
| AVBD solve | src/physics/gpu/avbdState.ts |
| LBVH builder | src/lvbh/GPULBVHBuilder.ts |
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
substeps(e.g., 20) for stiff stacks or fast-moving bodies - Increase
iterationsfor better constraint convergence - Use
mass: 0for static bodies (never moves, acts as infinite mass) - Lower
stiffnessvalues for softer, more stable joints - Set
restitution: 0+ highfrictionfor non-bouncy stacking
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
chrome://flags/#enable-unsafe-webgpuon older versions - Firefox/Safari do not currently support WebGPU
Simulation Explodes / Bodies Flying Off
- Reduce timestep or increase
substeps - Lower joint
stiffnessvalues - Ensure static bodies have
mass: 0 - Check that
halfExtentsare positive and non-zero
Bodies Sinking Through Ground
- Increase
iterations(try 15–20) - 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
chrome://gputo ensure hardware acceleration is active
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