Colorful 3D physics objects bouncing across a low-poly landscape

Crashcat: Physics That Won't Crash Your Bundle

The Orange Cat
The Orange Cat

If you've ever wanted to add physics to a web project — a character running across terrain, objects tumbling down a hill, a door swinging on a hinge — you've probably stared down a familiar dilemma. The powerful engines come wrapped in multi-megabyte WASM binaries and demand manual memory management. The lightweight ones lack modern features. crashcat steps right into that gap: a pure JavaScript rigid body physics engine with continuous collision detection, shapecasting, constraints, and character controllers, all without a single byte of WASM.

Why Your Bundle Deserves Better

The state of 3D physics on the web has long been split into two camps. On one side sit WASM-based engines like Rapier and Ammo.js — fast and feature-rich, but shipping 1.5 to 3 megabytes of compiled binary that your users have to download before anything bounces. On the other side, pure JavaScript options like Cannon-es keep things light but miss modern essentials like continuous collision detection and shapecasting.

crashcat refuses to pick a side. It's written entirely in TypeScript, fully tree-shakeable, and garbage-collected like the rest of your JavaScript code. No FFI boundaries, no manual memory cleanup, no "alien" patterns. You get industry-standard GJK/EPA collision detection, a dynamic bounding volume hierarchy for broadphase, and eight constraint types — all in a package that only bundles the pieces you actually use.

What's in the Box

  • Rigid body simulation with static, dynamic, and kinematic motion types
  • Continuous collision detection to prevent fast objects from tunneling through walls
  • Eight constraint types: hinge, slider, distance, point, fixed, cone, swing-twist, and six-DOF
  • Kinematic character controller for player movement out of the box
  • Raycasting and shapecasting for line-of-sight checks and sweep queries
  • Sensor bodies for trigger zones without physical response
  • Body sleeping to save cycles on inactive objects
  • Debug visualization with built-in three.js support

Getting Started

Install via your package manager of choice:

npm install crashcat
yarn add crashcat

Your First Falling Box

Setting Up a World

Every physics simulation starts with a world. You configure gravity, broadphase layers, and object layers, then start creating bodies.

import {
  createWorld,
  rigidBody,
  box,
  MotionType,
  updateWorld,
} from "crashcat";

const LAYER_STATIC = 0;
const LAYER_DYNAMIC = 1;

const world = createWorld({
  gravity: [0, -9.81, 0],
});

The gravity vector uses SI units — meters per second squared — with a Y-up coordinate system. The default -9.81 gives you Earth-like gravity right away.

Adding a Ground Plane

Static bodies don't move but participate in collisions. A large box makes a simple ground plane:

rigidBody.create(world, {
  motionType: MotionType.STATIC,
  shape: box.create({ halfExtents: [50, 0.5, 50] }),
  position: [0, -0.5, 0],
  objectLayer: LAYER_STATIC,
});

Dropping Something On It

Dynamic bodies respond to gravity and collisions. Create a box above the ground and let physics do the rest:

const dynamicBox = rigidBody.create(world, {
  motionType: MotionType.DYNAMIC,
  shape: box.create({ halfExtents: [0.5, 0.5, 0.5] }),
  position: [0, 10, 0],
  objectLayer: LAYER_DYNAMIC,
});

Then step the simulation in your render loop:

function gameLoop(deltaTime: number) {
  updateWorld(world, deltaTime);

  const position = rigidBody.getPosition(world, dynamicBox);
  console.log("Box Y:", position[1]);
}

Physics runs at a fixed 60 Hz timestep internally, regardless of your rendering frame rate. Pass your real delta time and crashcat handles the accumulation and substeps for you.

Swinging Doors and Ragdoll Dreams

Constraints That Click

The eight constraint types let you build everything from swinging doors to complex mechanical rigs. Here's a hinge constraint connecting two bodies:

import { hinge } from "crashcat";

const doorFrame = rigidBody.create(world, {
  motionType: MotionType.STATIC,
  shape: box.create({ halfExtents: [0.1, 1, 0.5] }),
  position: [0, 1, 0],
  objectLayer: LAYER_STATIC,
});

const door = rigidBody.create(world, {
  motionType: MotionType.DYNAMIC,
  shape: box.create({ halfExtents: [0.5, 1, 0.05] }),
  position: [0.5, 1, 0],
  objectLayer: LAYER_DYNAMIC,
});

hinge.create(world, {
  bodyA: doorFrame,
  bodyB: door,
  anchor: [0, 1, 0],
  axis: [0, 1, 0],
});

The hinge axis points upward, so the door swings left and right like you'd expect. You can add angular limits and motors to control the range and force of the swing.

Raycasting for the Win

Need to check if a player can see an enemy? Fire a ray and see what it hits:

import { raycast } from "crashcat";

const hit = raycast(world, {
  origin: [0, 1.5, 0],
  direction: [0, 0, -1],
  maxDistance: 100,
});

if (hit) {
  console.log("Hit body at distance:", hit.distance);
  console.log("Surface normal:", hit.normal);
}

Raycasting is built on the same broadphase BVH that powers collision detection, so it's fast even with many bodies in the scene.

A Character That Moves

The built-in kinematic character controller handles the tricky parts of player movement — stepping up stairs, sliding along walls, staying grounded on slopes:

import { characterController, capsule } from "crashcat";

const player = rigidBody.create(world, {
  motionType: MotionType.KINEMATIC,
  shape: capsule.create({ radius: 0.3, halfHeight: 0.5 }),
  position: [0, 1, 0],
  objectLayer: LAYER_DYNAMIC,
});

const controller = characterController.create(world, {
  body: player,
  maxSlopeAngle: Math.PI / 4,
  maxStepHeight: 0.3,
});

function update(input: { x: number; z: number }) {
  characterController.setVelocity(world, controller, [
    input.x * 5,
    0,
    input.z * 5,
  ]);
}

No more writing your own ground detection and slope limiting. The KCC handles contact resolution so your capsule guy just runs around on the mesh — which, as the creator puts it, is often all you really need.

Plugging Into Your Renderer

Three.js Debug Mode

crashcat ships with a dedicated three.js integration for debug visualization. During development, overlay wireframes on your scene to see exactly what the physics engine sees:

import { createDebugRenderer } from "crashcat/three";
import * as THREE from "three";

const scene = new THREE.Scene();
const debugRenderer = createDebugRenderer(world, scene);

function renderLoop() {
  debugRenderer.update();
  renderer.render(scene, camera);
}

Press d at runtime to toggle the debug overlay on and off. In production, just remove the import — tree-shaking ensures none of the debug code ships to your users.

Any Renderer Works

Since crashcat is framework-agnostic, you can read body positions and rotations and apply them to whatever rendering system you prefer:

function syncToRenderer(bodyHandle: number, mesh: any) {
  const pos = rigidBody.getPosition(world, bodyHandle);
  const rot = rigidBody.getRotation(world, bodyHandle);

  mesh.position.set(pos[0], pos[1], pos[2]);
  mesh.quaternion.set(rot[0], rot[1], rot[2], rot[3]);
}

Whether you're using Babylon.js, PlayCanvas, or a custom WebGL renderer, the pattern stays the same: step the world, read transforms, apply them to your visuals.

When to Reach for Crashcat

crashcat isn't trying to replace Rapier for scenes with thousands of interacting bodies or complex vehicle simulations where every CPU cycle counts. Its sweet spot is the wide range of projects where you need real physics — collisions, constraints, character controllers — without paying the WASM tax.

Think interactive product pages, browser-based games, creative coding experiments, and any project where your loading spinner already lasts too long. If your physics needs fit comfortably within what pure JavaScript can handle at 60 Hz, crashcat lets you skip the WASM complexity entirely.

It's brand new, it's lightweight, and it treats JavaScript like a first-class citizen rather than a host for compiled binaries. Sometimes that's exactly what you need.