npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ue-too/dynamics

v0.14.1

Published

2D physics engine with rigid body dynamics and collision detection.

Readme

@ue-too/dynamics

2D physics engine with rigid body dynamics and collision detection.

npm version license

Experimental: This package is an experimental implementation. Please DO NOT use this in production.

Overview

@ue-too/dynamics provides a complete 2D physics simulation engine featuring rigid body dynamics, collision detection, constraint solving, and performance optimizations like spatial indexing and sleeping bodies.

Key Features

  • Rigid Body Physics: Linear and angular velocity, mass, moment of inertia
  • Collision Detection: Broad phase (spatial indexing) + narrow phase (SAT)
  • Collision Response: Impulse-based resolution with friction and restitution
  • Constraints: Pin joints (fixed and between bodies) with Baumgarte stabilization
  • Spatial Indexing: QuadTree, Dynamic Tree, and Sweep-and-Prune algorithms
  • Sleeping System: Automatically disable resting bodies for performance
  • Collision Filtering: Category-based filtering with masks and groups
  • Shape Types: Circles and convex polygons

Installation

Using Bun:

bun add @ue-too/dynamics

Using npm:

npm install @ue-too/dynamics

Quick Start

Here's a simple example creating a physics world with a falling ball:

import { World, Circle, Polygon } from '@ue-too/dynamics';

// Create a physics world (2000x2000 world size)
const world = new World(2000, 2000, 'dynamictree');

// Create static ground
const ground = new Polygon(
  { x: 0, y: -100 },                      // Position
  [                                        // Vertices (local space)
    { x: -1000, y: 0 },
    { x: 1000, y: 0 },
    { x: 1000, y: 50 },
    { x: -1000, y: 50 }
  ],
  0,      // Rotation
  100,    // Mass (ignored for static bodies)
  true    // isStatic
);
world.addRigidBody('ground', ground);

// Create dynamic ball
const ball = new Circle(
  { x: 0, y: 200 },  // Position
  20,                // Radius
  0,                 // Rotation
  10,                // Mass
  false              // isStatic
);
world.addRigidBody('ball', ball);

// Simulation loop (60 FPS)
setInterval(() => {
  world.step(1/60);  // deltaTime in seconds

  console.log('Ball position:', ball.position);
}, 16);

Core Concepts

Rigid Bodies

Rigid bodies are objects that don't deform. They have:

  • Position: World coordinates
  • Rotation: Angle in radians
  • Velocity: Linear velocity vector
  • Angular Velocity: Rotation speed in radians/second
  • Mass: Affects how much force is needed to move the body
  • Moment of Inertia: Resistance to rotational acceleration

Static vs Dynamic Bodies

  • Static: Don't move, infinite mass (walls, floors, platforms)
  • Dynamic: Move and respond to forces (players, projectiles, debris)
  • Kinematic: Move but don't respond to collisions (moving platforms)

Collision Detection Phases

  1. Broad Phase: Uses spatial indexing to quickly find potentially colliding pairs

    • QuadTree: Good for static worlds
    • Dynamic Tree: Good for mixed static/dynamic
    • Sweep-and-Prune: Best for many dynamic bodies
  2. Narrow Phase: Precise collision detection using Separating Axis Theorem (SAT)

Collision Filtering

Bodies can be filtered by:

  • Category: What category this body belongs to (bit flags)
  • Mask: Which categories this body collides with (bit flags)
  • Group: Positive groups collide only with same group, negative never collide

Core APIs

World Class

Main physics world container.

Constructor:

const world = new World(
  worldWidth: number,
  worldHeight: number,
  spatialIndexType?: 'quadtree' | 'dynamictree' | 'sap'
);

Methods:

  • step(deltaTime: number): Advance simulation by deltaTime seconds
  • addRigidBody(id: string, body: RigidBody): Add a rigid body
  • removeRigidBody(id: string): Remove a rigid body
  • addConstraint(constraint: Constraint): Add a constraint
  • removeConstraint(constraint: Constraint): Remove a constraint
  • setSpatialIndexType(type): Change spatial indexing algorithm
  • queryAABB(aabb): RigidBody[]: Find all bodies in an AABB region
  • queryPoint(point): RigidBody[]: Find all bodies containing a point
  • rayCast(from, to): RayCastResult[]: Ray casting

Properties:

  • gravity: Point: World gravity vector (default: {x: 0, y: -9.8})
  • sleepingEnabled: boolean: Enable/disable sleeping system
  • bodies: Map<string, RigidBody>: All bodies in the world
  • constraints: Constraint[]: All constraints

Circle Class

Circular rigid body.

Constructor:

const circle = new Circle(
  position: Point,
  radius: number,
  rotation: number,
  mass: number,
  isStatic: boolean
);

Properties:

  • position: Point: World position
  • velocity: Point: Linear velocity
  • rotation: number: Rotation in radians
  • angularVelocity: number: Rotation speed
  • mass: number: Mass (0 for static bodies)
  • radius: number: Circle radius
  • restitution: number: Bounciness (0-1)
  • friction: number: Friction coefficient
  • collisionFilter: CollisionFilter: Filtering

Polygon Class

Convex polygon rigid body.

Constructor:

const polygon = new Polygon(
  position: Point,
  vertices: Point[],        // Local space vertices
  rotation: number,
  mass: number,
  isStatic: boolean
);

Properties:

  • Same as Circle, plus:
  • vertices: Point[]: Vertices in local space
  • worldVertices: Point[]: Vertices in world space (computed)

Methods:

  • updateWorldVertices(): Recompute world vertices after transform changes

Constraints

PinJoint

Pin joint between two bodies.

const joint = new PinJoint(
  bodyA: RigidBody,
  bodyB: RigidBody,
  anchorA: Point,  // Local anchor on bodyA
  anchorB: Point   // Local anchor on bodyB
);

FixedPinJoint

Pin joint from body to world point.

const fixedJoint = new FixedPinJoint(
  body: RigidBody,
  localAnchor: Point,  // Local anchor on body
  worldAnchor: Point   // Fixed world position
);

Collision Filter

type CollisionFilter = {
  category: number;    // What category this body is (bit flags)
  mask: number;        // What categories to collide with (bit flags)
  group: number;       // Collision group
};

Predefined Categories:

enum CollisionCategory {
  STATIC = 0x0001,
  PLAYER = 0x0002,
  ENEMY = 0x0004,
  PROJECTILE = 0x0008,
  SENSOR = 0x0010,
  PLATFORM = 0x0020
}

Common Use Cases

Basic Platformer Physics

import { World, Circle, Polygon, CollisionCategory } from '@ue-too/dynamics';

const world = new World(2000, 2000, 'dynamictree');
world.gravity = { x: 0, y: -20 }; // Downward gravity

// Ground
const ground = new Polygon(
  { x: 0, y: -150 },
  [{ x: -500, y: 0 }, { x: 500, y: 0 }, { x: 500, y: 50 }, { x: -500, y: 50 }],
  0, 0, true
);
ground.collisionFilter = {
  category: CollisionCategory.STATIC,
  mask: 0xFFFF, // Collides with everything
  group: 0
};
world.addRigidBody('ground', ground);

// Player
const player = new Circle({ x: 0, y: 0 }, 20, 0, 10, false);
player.collisionFilter = {
  category: CollisionCategory.PLAYER,
  mask: CollisionCategory.STATIC | CollisionCategory.PLATFORM | CollisionCategory.ENEMY,
  group: 0
};
player.restitution = 0; // No bounce
player.friction = 0.5;
world.addRigidBody('player', player);

// Apply jump force
function jump() {
  player.velocity.y = 15; // Upward velocity
}

// Game loop
function update(deltaTime: number) {
  world.step(deltaTime);
  // Render player at player.position
}

Pendulum with Constraints

import { World, Circle, FixedPinJoint } from '@ue-too/dynamics';

const world = new World(2000, 2000);
world.gravity = { x: 0, y: -9.8 };

// Pendulum bob
const bob = new Circle({ x: 0, y: 100 }, 20, 0, 10, false);
bob.restitution = 0.8; // Bouncy
world.addRigidBody('bob', bob);

// Fix to world origin
const joint = new FixedPinJoint(
  bob,
  { x: 0, y: 0 },  // Bob's center
  { x: 0, y: 0 }   // World origin
);
world.addConstraint(joint);

// Simulation
function update(deltaTime: number) {
  world.step(deltaTime);
}

Chain of Bodies

import { World, Circle, PinJoint } from '@ue-too/dynamics';

const world = new World(2000, 2000);
world.gravity = { x: 0, y: -9.8 };

const links: Circle[] = [];
const numLinks = 5;

// Create chain links
for (let i = 0; i < numLinks; i++) {
  const link = new Circle({ x: i * 30, y: 0 }, 10, 0, 5, false);
  world.addRigidBody(`link${i}`, link);
  links.push(link);

  if (i > 0) {
    // Connect to previous link
    const joint = new PinJoint(
      links[i - 1],
      links[i],
      { x: 10, y: 0 },  // Right edge of previous
      { x: -10, y: 0 }  // Left edge of current
    );
    world.addConstraint(joint);
  }
}

// Fix first link to world
const fixedJoint = new FixedPinJoint(
  links[0],
  { x: -10, y: 0 },
  { x: 0, y: 0 }
);
world.addConstraint(fixedJoint);

Collision Sensors

import { Circle, CollisionCategory } from '@ue-too/dynamics';

// Create a trigger zone that doesn't physically collide
const trigger = new Circle({ x: 100, y: 100 }, 50, 0, 0, true);
trigger.collisionFilter = {
  category: CollisionCategory.SENSOR,
  mask: CollisionCategory.PLAYER,
  group: -1  // Negative group = never physically collide
};
world.addRigidBody('trigger', trigger);

// Listen for collisions
world.onCollision((bodyA, bodyB, contacts) => {
  if (bodyA === trigger || bodyB === trigger) {
    console.log('Player entered trigger zone!');
  }
});

Spatial Queries

// Find all bodies in a region
const aabb = {
  min: { x: -50, y: -50 },
  max: { x: 50, y: 50 }
};
const bodiesInRegion = world.queryAABB(aabb);

// Find bodies at a point
const bodiesAtPoint = world.queryPoint({ x: 100, y: 100 });

// Ray cast
const rayResults = world.rayCast(
  { x: 0, y: 0 },     // From
  { x: 100, y: 100 }  // To
);

rayResults.forEach(result => {
  console.log('Hit:', result.body, 'at distance:', result.distance);
});

Performance Tuning

import { World } from '@ue-too/dynamics';

const world = new World(2000, 2000, 'sap'); // Sweep-and-prune for many dynamic bodies

// Enable sleeping
world.sleepingEnabled = true;

// Customize sleeping thresholds per body
body.sleepThreshold = 0.01;  // Velocity threshold
body.sleepTime = 0.5;         // Seconds at rest before sleeping

// Get performance stats
const stats = world.getCollisionStats();
console.log('Broad phase pairs:', stats.broadPhasePairs);
console.log('Narrow phase tests:', stats.narrowPhaseTests);
console.log('Active collisions:', stats.activeCollisions);
console.log('Sleeping bodies:', stats.sleepingBodies);

// Switch spatial index at runtime
world.setSpatialIndexType('dynamictree');

API Reference

For complete API documentation with detailed type information, see the TypeDoc-generated documentation.

TypeScript Support

This package is written in TypeScript with complete type definitions:

import {
  World,
  Circle,
  Polygon,
  RigidBody,
  Constraint,
  CollisionCategory,
  type Point
} from '@ue-too/dynamics';

// Bodies are fully typed
const circle: Circle = new Circle({ x: 0, y: 0 }, 20, 0, 10, false);
const polygon: Polygon = new Polygon(/* ... */);

// Constraints are typed
const joint: Constraint = new PinJoint(circle, polygon, { x: 0, y: 0 }, { x: 0, y: 0 });

// Filters are typed
circle.collisionFilter = {
  category: CollisionCategory.PLAYER,
  mask: CollisionCategory.STATIC | CollisionCategory.ENEMY,
  group: 0
};

Design Philosophy

This physics engine follows these principles:

  • Simplicity: Focus on common 2D game physics use cases
  • Performance: Spatial indexing and sleeping for scalability
  • Modularity: Pluggable spatial index algorithms
  • Practicality: Designed for games, not scientific simulation
  • Type Safety: Full TypeScript support

Performance Considerations

  • Spatial Indexing: Choose based on your use case:

    • QuadTree: Static worlds with few dynamic objects
    • Dynamic Tree: Mixed static/dynamic (recommended default)
    • Sweep-and-Prune: Many dynamic objects moving continuously
  • Sleeping System: Automatically disables physics for resting bodies

  • Collision Filtering: Reduces narrow phase tests significantly

  • Fixed Time Step: Use fixed time steps (1/60) for stability

Performance Tips:

  • Enable sleeping for worlds with many resting bodies
  • Use collision filtering to avoid unnecessary collision tests
  • Choose appropriate spatial index for your scenario
  • Avoid very large mass ratios between bodies (causes instability)
  • Use static bodies for immovable objects
  • Limit polygon vertex counts (4-8 vertices is optimal)

Limitations

  • 2D Only: No 3D support
  • Convex Polygons: Concave shapes must be decomposed
  • No Continuous Collision: Fast-moving objects may tunnel
  • Simple Friction Model: Basic static and dynamic friction
  • Experimental: Not production-ready, API may change

Debugging Tips

// Enable debug rendering
world.debugDraw = (ctx: CanvasRenderingContext2D) => {
  // Draw all bodies
  world.bodies.forEach(body => {
    ctx.strokeStyle = body.isStatic ? 'gray' : 'blue';
    if (body instanceof Circle) {
      ctx.beginPath();
      ctx.arc(body.position.x, body.position.y, body.radius, 0, Math.PI * 2);
      ctx.stroke();
    } else if (body instanceof Polygon) {
      ctx.beginPath();
      ctx.moveTo(body.worldVertices[0].x, body.worldVertices[0].y);
      for (let i = 1; i < body.worldVertices.length; i++) {
        ctx.lineTo(body.worldVertices[i].x, body.worldVertices[i].y);
      }
      ctx.closePath();
      ctx.stroke();
    }
  });
};

Related Packages

  • @ue-too/math: Vector operations used throughout the physics engine
  • @ue-too/ecs: Entity Component System that can integrate with physics
  • @ue-too/board: Canvas board for rendering physics simulations

Further Reading

License

MIT

Repository

https://github.com/ue-too/ue-too