PromptHub
Game Development JavaScript Libraries

Physics Engines! Crashcat Makes JS Simulations Effortless

B

Bright Coding

Author

16 min read
5 views
Physics Engines! Crashcat Makes JS Simulations Effortless

Stop Wrestling with Physics Engines! Crashcat Makes JS Simulations Effortless

What if I told you that the most painful part of building a JavaScript game isn't the rendering, the networking, or even the AI—it's the physics engine that brings everything crashing down? You've been there: spending weeks wrestling with bloated C++ bindings that break on every Node.js update, watching your bundle size balloon past 500KB for a simple stacking game, or discovering that your fast-moving projectiles are ghosting straight through walls because tunneling detection costs extra. The JavaScript ecosystem has been starved for a physics solution that respects the platform—until now.

Enter crashcat, the pure JavaScript physics engine that's making developers abandon their WASM nightmares and C++ dependency chains. Built by Isaac Mason and designed specifically for games, simulations, and creative websites, crashcat delivers professional-grade rigid body simulation without the platform baggage. No Emscripten. No memory management nightmares. Just clean, tree-shakeable JavaScript that works with Three.js, Babylon.js, PlayCanvas, or whatever renderer you love. In this deep dive, I'll expose why crashcat is secretly becoming the weapon of choice for developers who refuse to compromise on performance or bundle size—and exactly how to harness its power for your next project.

What is Crashcat?

Crashcat is a pure JavaScript physics engine purpose-built for interactive 3D applications running in browsers, Node.js, or any JavaScript environment. Created by developer Isaac Mason and available at github.com/isaac-mason/crashcat, it represents a deliberate rejection of the industry's WASM-heavy approach to physics simulation.

The project's philosophy is radical in its simplicity: physics engines don't need to be black boxes compiled from C++. By writing everything in highly optimized JavaScript, crashcat achieves something its competitors can't match—genuine tree-shakeability where you only ship the physics features your game actually uses. No dead code elimination struggles with foreign function interfaces. No shipping entire physics SDKs because you needed one constraint type.

Crashcat is trending now because the JavaScript game development landscape has matured dramatically. WebGPU is unlocking console-quality rendering. Tools like Three.js R3F and Babylon.js are production-ready for serious games. But physics remained the awkward outlier—developers were forced to choose between limited pure-JS solutions or heavyweight WASM ports that fought against the JavaScript ecosystem. Crashcat closes this gap with a feature set that rivals professional engines: continuous collision detection, dynamic BVH broadphase acceleration, comprehensive constraint systems with motors and springs, and flexible collision filtering—all while maintaining the ergonomics that JavaScript developers expect.

The engine's architecture reveals deep technical sophistication beneath its accessible API. It uses a two-tier spatial partitioning system (broadphase layers plus object layers) that lets you optimize collision detection for your specific scene structure. Its sleeping system automatically freezes settled bodies to preserve CPU cycles. And its listener system gives you surgical control over every physics event, from pre-narrowphase filtering to per-contact friction modification. This isn't a toy physics engine—it's a production tool that happens to be written in JavaScript.

Key Features That Separate Crashcat from the Pack

Let's dissect what makes crashcat technically special, because the feature list alone doesn't capture the engineering decisions that matter for real projects.

Rigid Body Simulation with Full Motion Type Support: Crashcat handles static, dynamic, and kinematic bodies with proper physical interaction. Kinematic bodies push dynamic bodies without being pushed back—essential for moving platforms, elevators, and scripted animations. The moveKinematic API computes proper velocities rather than teleporting, ensuring physically correct interactions.

Advanced Shape System: Beyond basic spheres and boxes, crashcat supports convex hulls, capsules, cylinders, triangle meshes, compound shapes, and custom shapes via a decorator pattern. The convex radius optimization on all convex shapes trades microscopic geometric accuracy for significant performance gains—a smart default that most games benefit from.

Continuous Collision Detection (CCD): Fast-moving objects like bullets or racing cars use linear cast CCD to prevent tunneling through thin geometry. This is configurable per-body via MotionQuality.LINEAR_CAST, with threshold tuning in world settings.

Constraint Arsenal with Motors and Springs: Hinge, slider, distance, point, fixed, cone, swing-twist, and six-DOF constraints—all with motor support for powered joints and spring behavior for soft constraints. This covers everything from doors and wheels to ragdolls and robotic arms.

Dynamic BVH Broadphase: Spatial acceleration isn't static. Crashcat's broadphase rebuilds its bounding volume hierarchy dynamically, maintaining query performance as objects move. The two-layer system (broadphase layers for spatial partitioning, object layers for collision filtering) lets you optimize for your specific access patterns.

Flexible Collision Filtering: Three mechanisms work together: object layers (fast, defines collision matrix), collision groups/masks (32-bit bitmasks for category-based filtering), and onBodyPairValidate callbacks (custom logic before narrowphase). This layered approach lets you start simple and add complexity only where needed.

Tree-Shakeable Pure JavaScript: Perhaps crashcat's most distinctive advantage. Because it's pure JavaScript with explicit registration functions (registerAll() or granular registerShapes/registerConstraints), your bundler can eliminate unused code. The engine works with any renderer—no adapter layers, no engine lock-in.

Real-World Use Cases Where Crashcat Dominates

Browser-Based 3D Games

Indie developers building web-first games face a brutal choice: use a limited physics solution or accept massive bundle sizes and WASM complexity. Crashcat eliminates this trade-off. A platformer with moving platforms, collectible items, and enemy AI needs kinematic bodies, sensor triggers, and raycasts for ground detection—crashcat handles all of this in ~50KB of tree-shaken JavaScript.

Interactive Product Configurators

E-commerce sites letting customers customize furniture, vehicles, or machinery need physics for realistic assembly previews. Crashcat's compound shapes model complex objects, while constraints demonstrate mechanical relationships. The pure-JS deployment means zero friction with existing React/Vue/Angular build pipelines.

Physics Simulations and Creative Coding

Generative artists and data visualization creators need reliable physics without game-engine overhead. Crashcat's deterministic simulation (with fixed timestep) enables reproducible art pieces. Its query system (raycast, shape sweep, point test) supports interaction patterns like mouse-picking and spatial selection.

Multiplayer Web Games with Server Authority

Node.js compatibility means crashcat can run physics on the server for authoritative multiplayer. The world state serialization support enables snapshot-based networking. Pure JavaScript deployment simplifies DevOps compared to WASM modules with platform-specific binaries.

Educational Physics Demonstrations

The clean API and comprehensive examples make crashcat ideal for teaching physics concepts. From simple projectile motion to complex constraint systems, the progressive disclosure of features matches pedagogical needs.

Step-by-Step Installation & Setup Guide

Getting crashcat running takes under five minutes. Here's the complete setup:

Installation

npm install crashcat

No native dependencies. No post-install scripts. No platform-specific binaries. This single command works on macOS, Windows, Linux, and in CI environments.

Basic Project Structure

Create a TypeScript file for your world settings (recommended pattern from the documentation):

// physics-settings.ts
import {
    addBroadphaseLayer,
    addObjectLayer,
    createWorldSettings,
    enableCollision,
    registerAll,
} from 'crashcat';

// Register all shape and constraint types
// For production, replace with granular registrations for smaller bundles
registerAll();

export const worldSettings = createWorldSettings();

// Configure gravity (Earth standard)
worldSettings.gravity = [0, -9.81, 0];

// Define broadphase layers for spatial partitioning
export const BROADPHASE_LAYER_MOVING = addBroadphaseLayer(worldSettings);
export const BROADPHASE_LAYER_NOT_MOVING = addBroadphaseLayer(worldSettings);

// Define object layers for collision filtering
export const OBJECT_LAYER_MOVING = addObjectLayer(worldSettings, BROADPHASE_LAYER_MOVING);
export const OBJECT_LAYER_NOT_MOVING = addObjectLayer(worldSettings, BROADPHASE_LAYER_NOT_MOVING);

// Establish collision rules
enableCollision(worldSettings, OBJECT_LAYER_MOVING, OBJECT_LAYER_NOT_MOVING);
enableCollision(worldSettings, OBJECT_LAYER_MOVING, OBJECT_LAYER_MOVING);

Creating the Physics World

// world.ts
import { createWorld } from 'crashcat';
import { worldSettings } from './physics-settings';

export const world = createWorld(worldSettings);

Game Loop Integration

For production games, use fixed timestep with interpolation:

// game-loop.ts
import { updateWorld } from 'crashcat';
import { world } from './world';

const PHYSICS_DT = 1 / 60;
let accumulator = 0;
let lastTime = performance.now();

function gameLoop() {
    const currentTime = performance.now();
    const frameTime = Math.min((currentTime - lastTime) / 1000, 0.25); // Cap at 250ms
    lastTime = currentTime;

    accumulator += frameTime;

    // Fixed physics steps for determinism
    while (accumulator >= PHYSICS_DT) {
        updateWorld(world, undefined, PHYSICS_DT);
        accumulator -= PHYSICS_DT;
    }

    // Calculate interpolation factor for smooth rendering
    const alpha = accumulator / PHYSICS_DT;
    
    // Render with interpolated positions...
    // renderer.render(scene, camera, alpha);

    requestAnimationFrame(gameLoop);
}

gameLoop();

Environment Considerations

  • Coordinate System: Crashcat uses OpenGL conventions—Y-up, right-handed, meters for length. Transform if your renderer differs.
  • Scale: A halfExtents: [1, 1, 1] box is 2 meters per side. Skyscraper-sized objects fall slowly relative to their size—keep dimensions realistic.
  • Triangle Winding: Counter-clockwise is front-facing for mesh shapes.

REAL Code Examples from Crashcat

Let's examine production-ready patterns extracted directly from crashcat's documentation, with detailed explanations of the engineering decisions embedded in each.

Example 1: Complete World Setup with Layer Configuration

This is the foundation pattern every crashcat project needs. The two-tier layer system is the engine's secret weapon for scalable performance:

import {
    addBroadphaseLayer,
    addObjectLayer,
    box,
    type CollideShapeHit,
    type ContactManifold,
    type ContactSettings,
    ContactValidateResult,
    createWorld,
    createWorldSettings,
    enableCollision,
    type Listener,
    MotionType,
    type RigidBody,
    registerAll,
    rigidBody,
    updateWorld,
} from 'crashcat';
import type { Vec3 } from 'mathcat';

// Register all shapes and constraints
// In production, use registerShapes/registerConstraints for tree-shaking
registerAll();

// Create settings container—separate from world creation for clean imports
const worldSettings = createWorldSettings();

// BROADPHASE LAYERS: partition space for the dynamic BVH
// Moving bodies get their own tree for efficient updates
export const BROADPHASE_LAYER_MOVING = addBroadphaseLayer(worldSettings);
// Static bodies rarely change, so they get a separate tree
export const BROADPHASE_LAYER_NOT_MOVING = addBroadphaseLayer(worldSettings);

// OBJECT LAYERS: control WHICH bodies can collide
// Each maps to exactly one broadphase layer for spatial placement
export const OBJECT_LAYER_MOVING = addObjectLayer(worldSettings, BROADPHASE_LAYER_MOVING);
export const OBJECT_LAYER_NOT_MOVING = addObjectLayer(worldSettings, BROADPHASE_LAYER_NOT_MOVING);

// Define collision matrix: moving hits everything, static only hits moving
enableCollision(worldSettings, OBJECT_LAYER_MOVING, OBJECT_LAYER_NOT_MOVING);
enableCollision(worldSettings, OBJECT_LAYER_MOVING, OBJECT_LAYER_MOVING);
// Note: static-static collisions are disabled—saves CPU since neither moves

// Physics configuration
worldSettings.gravity = [0, -9.81, 0];

// Instantiate the simulation world
const world = createWorld(worldSettings);

// Create static ground plane
rigidBody.create(world, {
    motionType: MotionType.STATIC,
    shape: box.create({ halfExtents: [10, 1, 10] }), // 20x2x20 meter ground
    objectLayer: OBJECT_LAYER_NOT_MOVING,
});

// Stack of dynamic boxes to test stability
for (let i = 0; i < 5; i++) {
    rigidBody.create(world, {
        motionType: MotionType.DYNAMIC,
        shape: box.create({ halfExtents: [1, 1, 1] }),
        objectLayer: OBJECT_LAYER_MOVING,
        position: [0, 2 + i * 2, 0], // Stack 2 meters apart vertically
    });
}

// Simple simulation loop (60 FPS for 10 seconds)
for (let i = 0; i < 60 * 10; i++) {
    // undefined = no listener; 1/60 = fixed timestep
    updateWorld(world, undefined, 1 / 60);
}

Why this matters: The separation of worldSettings from createWorld enables configuration reuse across multiple worlds. The two-tier layer system means adding 1000 static terrain pieces doesn't slow down queries for moving characters—each broadphase layer maintains its own BVH tree.

Example 2: Safe Body Reference Pattern with ID-Based Lookup

This example reveals a critical performance optimization that bites developers coming from object-oriented physics engines:

// ❌ DANGEROUS: Direct references become stale when bodies are pooled
interface BadEntity {
    body: RigidBody; // Reference becomes invalid after body removal/reuse
}

// ✅ CORRECT: Store IDs, lookup fresh references when needed
interface GoodEntity {
    bodyId: number; // Stable across pool operations
}

// Creating a body and storing its ID
const dynamicBox = rigidBody.create(world, {
    shape: box.create({ halfExtents: [1, 1, 1] }),
    motionType: MotionType.DYNAMIC,
    objectLayer: OBJECT_LAYER_MOVING,
    position: [0, 5, 0],
});

const storedId = dynamicBox.id; // Store this, not the body object

// Later, in game logic or collision callbacks:
const bodyById = rigidBody.get(world, storedId);

if (bodyById) {
    // Safe to use—verified the body still exists
    rigidBody.addForce(world, bodyById, [0, 10, 0], true); // true = wake if sleeping
}

The engineering insight: Crashcat pools rigid body objects internally for cache-friendly memory layout and zero-allocation hot paths. When a body is removed, its slot may be reused with a different sequence number. The id encodes both index and generation, making stale access detectable. This pattern eliminates an entire class of use-after-free bugs that plague game physics.

Example 3: Kinematic Platform with Proper Velocity-Based Movement

Moving platforms are notoriously tricky—naive position-setting causes physics explosions. Crashcat's moveKinematic solves this:

import { vec3, quat } from 'gl-matrix'; // or your math library of choice

// Create kinematic platform (pushes dynamic bodies, not pushed back)
const platform = rigidBody.create(world, {
    shape: box.create({ halfExtents: [2, 0.2, 2] }), // Wide, thin platform
    motionType: MotionType.KINEMATIC,
    objectLayer: OBJECT_LAYER_MOVING, // Can interact with other moving objects
    position: [0, 2, 0],
});

// Animation state
let time = 0;
const PLATFORM_SPEED = 2; // meters per second
const PLATFORM_RANGE = 5; // meters from center

function updatePlatform(deltaTime: number) {
    time += deltaTime;
    
    // Calculate target position: horizontal oscillation
    const targetPosition = vec3.fromValues(
        Math.sin(time * PLATFORM_SPEED) * PLATFORM_RANGE,
        2, // Constant height
        0
    );
    
    // Slight rotation for visual interest
    const targetQuaternion = quat.create();
    quat.setAxisAngle(targetQuaternion, vec3.fromValues(0, 1, 0), Math.sin(time) * 0.1);
    
    // CRITICAL: moveKinematic computes velocities, not teleportation
    // Dynamic bodies on platform will be pushed with correct friction
    rigidBody.moveKinematic(platform, targetPosition, targetQuaternion, deltaTime);
}

// In your game loop:
// updatePlatform(PHYSICS_DT);
// updateWorld(world, undefined, PHYSICS_DT);

Why moveKinematic beats setTransform: Direct position setting teleports the body, causing dynamic bodies on top to lose contact and fall through. moveKinematic computes the velocity needed to reach the target, maintaining continuous contact and generating proper friction forces. The platform can carry boxes, characters, or vehicles without jitter.

Example 4: Advanced Collision Filtering with Bitmasks

When object layers aren't granular enough, crashcat's bitmask system provides fine-grained control:

import { bitmask } from 'crashcat';

// Define named collision categories
const GROUPS = bitmask.createFlags([
    'player',
    'enemy', 
    'debris',
    'projectile',
    'trigger'
] as const);

// Player: collides with enemies, projectiles, and triggers
const playerBody = rigidBody.create(world, {
    shape: box.create({ halfExtents: [0.5, 1, 0.5] }),
    motionType: MotionType.DYNAMIC,
    objectLayer: OBJECT_LAYER_MOVING,
    collisionGroups: GROUPS.player,      // "I am a player"
    collisionMask: GROUPS.enemy | GROUPS.projectile | GROUPS.trigger, // "I hit these"
});

// Enemy: collides with player and debris, but NOT other enemies
const enemyBody = rigidBody.create(world, {
    shape: sphere.create({ radius: 1 }),
    motionType: MotionType.DYNAMIC,
    objectLayer: OBJECT_LAYER_MOVING,
    collisionGroups: GROUPS.enemy,
    collisionMask: GROUPS.player | GROUPS.debris, // Friendly fire disabled!
});

// Projectile: collides with enemies and triggers only
const projectileBody = rigidBody.create(world, {
    shape: sphere.create({ radius: 0.1 }),
    motionType: MotionType.DYNAMIC,
    objectLayer: OBJECT_LAYER_MOVING,
    collisionGroups: GROUPS.projectile,
    collisionMask: GROUPS.enemy | GROUPS.trigger,
    motionQuality: MotionQuality.LINEAR_CAST, // CCD for fast projectiles
});

// Trigger zone: sensor that detects player and enemies
const triggerZone = rigidBody.create(world, {
    shape: box.create({ halfExtents: [5, 5, 5] }),
    motionType: MotionType.STATIC,
    objectLayer: OBJECT_LAYER_NOT_MOVING,
    sensor: true, // No physical response, just detection
    collisionGroups: GROUPS.trigger,
    collisionMask: GROUPS.player | GROUPS.enemy,
});

Collision rule: Two bodies collide only if (groupA & maskB) !== 0 AND (groupB & maskA) !== 0. This symmetric AND means both parties must consent to the collision. Combined with object layer filtering (which runs first), you get two-stage filtering for maximum performance.

Advanced Usage & Best Practices

Fixed Timestep with Accumulator: Never use variable delta time for physics. The fixed timestep pattern in the setup section ensures deterministic, stable simulation. Interpolate visual positions using the alpha remainder for buttery smooth rendering at any framerate.

Shape Reuse for Memory Efficiency: Create shapes once, reuse across bodies. A shared box shape for 1000 debris pieces eliminates per-body shape allocation:

const debrisShape = box.create({ halfExtents: [0.2, 0.2, 0.2] });
// Reuse debrisShape for all explosion fragments

Offline Mesh Processing: Triangle meshes perform sanitization, active edge computation, and BVH construction at creation time. Do this in your build step, serialize to JSON, and load pre-processed shapes at runtime:

// Build step (Node.js)
const terrainShape = triangleMesh.create({ positions, indices });
fs.writeFileSync('terrain.json', JSON.stringify(terrainShape));

// Runtime (browser)
const terrainShape = JSON.parse(await fetch('terrain.json').then(r => r.text()));

Listener Performance Hierarchy: For fastest filtering, prefer object layers > collision groups > onBodyPairValidate > onContactValidate. Each step runs later in the pipeline and costs more.

Sleeping Strategy: Enable sleeping for stable stacks and resting objects. Wake regions after explosions or level changes with rigidBody.wakeInAABB for targeted activation.

Comparison with Alternatives

Feature Crashcat Cannon-es Ammo.js (Bullet WASM) Rapier (Rust WASM)
Language Pure JavaScript JavaScript C++ → WASM Rust → WASM
Bundle Size (min+gz) ~15-50KB* ~90KB ~300KB+ ~150KB+
Tree Shakeable ✅ Native ❌ No ❌ No ⚠️ Partial
CCD (Tunneling) ✅ Built-in ❌ No ✅ Yes ✅ Yes
Constraint Motors ✅ Full ⚠️ Limited ✅ Yes ✅ Yes
Kinematic Body API moveKinematic ⚠️ Manual ✅ Yes ✅ Yes
Node.js Server ✅ Yes ✅ Yes ⚠️ Binary deps ✅ Yes
Renderer Lock-in ❌ None ❌ None ❌ None ❌ None
Determinism ✅ Fixed timestep ⚠️ Variable ✅ Yes ✅ Yes
Active Development ✅ 2024+ ⚠️ Maintenance ❌ Stagnant ✅ Active

*Depends on features used; granular registration enables smaller bundles

Why choose crashcat? When you need professional physics without WASM complexity, when bundle size matters for web deployment, when you want deterministic server-side simulation without binary dependencies, or when you simply prefer debugging JavaScript over compiled foreign code.

FAQ

Is crashcat suitable for large open-world games? The dynamic BVH broadphase scales well, but extremely large worlds may need streaming or spatial partitioning at the application level. For most web games and indie projects, performance is excellent.

Can I use crashcat with React Three Fiber? Absolutely. Crashcat is renderer-agnostic. Create bodies in useEffect hooks, update mesh transforms from body positions in useFrame, and you're set. No special adapter needed.

How does crashcat handle determinism? Use fixed timestep with the accumulator pattern. Same inputs produce same outputs across runs. For networked games, serialize world state and replay on clients.

What's the performance cost of sensor bodies? Sensors participate in broadphase and narrowphase detection but skip contact constraint solving. They're nearly free for trigger zones and pickup detection.

Can I modify collision response per-contact? Yes! The onContactAdded listener receives ContactSettings that let you override friction, restitution, and even the collision normal for that specific contact pair.

Is there Web Worker support? Since crashcat is pure JavaScript, it runs anywhere JavaScript runs—including Web Workers and Node.js worker threads. Transfer world state as ArrayBuffers for zero-copy parallelism.

How do I debug physics? Crashcat includes a debug renderer example showing body AABBs, contact points, and constraint visualization. Integrate similar visualization into your renderer for development builds.

Conclusion

Crashcat represents a maturation point for JavaScript game development—a physics engine that treats the web platform as a first-class citizen rather than a compilation target. Its pure JavaScript architecture eliminates the friction that has historically made physics the hardest part of web game development: no WASM loading, no memory management, no platform-specific builds, just code that runs where JavaScript runs.

The technical depth is there when you need it. Continuous collision detection for competitive multiplayer. Constraint motors for mechanical simulation. Bitmask filtering for complex gameplay logic. But the surface is approachable—a developer can have a stack of boxes tumbling in minutes, then progressively adopt advanced features as their project demands.

If you're building games, simulations, or interactive experiences in JavaScript and you've been settling for physics solutions that fight against your stack, stop compromising. The repository at github.com/isaac-mason/crashcat contains everything you need: comprehensive documentation, 40+ live examples, and an API designed by someone who actually ships web games. Install it, run the quick start, and feel the difference of physics that works with your JavaScript ecosystem instead of against it.

Comments (0)

Comments are moderated before appearing.

No comments yet. Be the first to share your thoughts!

Support us! ☕