Skip to content

NewKrok/nape-js

Repository files navigation

nape-js logo

@newkrok/nape-js

npm version npm downloads CI bundle size license docs

Fully typed, tree-shakeable 2D physics engine — a modern TypeScript rewrite of the Nape Haxe physics engine.

Homepage & Interactive Demos | API Reference | Examples | Multiplayer Demo

Cookbook | Troubleshooting | Anti-Patterns

  • Originally created in Haxe by Luca Deltodesco
  • Ported to TypeScript by Istvan Krisztian Somoracz

Packages

This repo is an npm-workspaces monorepo:

Package What it is
@newkrok/nape-js The physics engine itself — rigid bodies, constraints, collision, fluids, deterministic multiplayer.
@newkrok/nape-pixi PixiJS v8 integration — BodySpriteBinding, FixedStepper (render interpolation), PixiDebugDraw, WorkerBridge.

Installation

npm install @newkrok/nape-js
# optional: PixiJS v8 integration
npm install @newkrok/nape-pixi pixi.js

Quick Start

import { Space, Body, BodyType, Vec2, Circle, Polygon } from "@newkrok/nape-js";

// Create a physics world with downward gravity
const space = new Space(new Vec2(0, 600));

// Static floor
const floor = new Body(BodyType.STATIC, new Vec2(400, 550));
floor.shapes.add(new Polygon(Polygon.box(800, 20)));
floor.space = space;

// Dynamic box
const box = new Body(BodyType.DYNAMIC, new Vec2(400, 100));
box.shapes.add(new Polygon(Polygon.box(40, 40)));
box.space = space;

// Dynamic circle
const ball = new Body(BodyType.DYNAMIC, new Vec2(420, 50));
ball.shapes.add(new Circle(20));
ball.space = space;

// Game loop
function update() {
  space.step(1 / 60);

  for (const body of space.bodies) {
    console.log(`x=${body.position.x.toFixed(1)} y=${body.position.y.toFixed(1)}`);
  }
}

API Reference

Full API documentation: TypeDoc Reference

Core Classes

Class Description
Space Physics world — add bodies, step simulation, deterministic mode for rollback/prediction
Body Rigid body with position, velocity, mass
Vec2 2D vector — pooling, clone(), equals(), lerp(), fromAngle()
Vec3 3D vector for constraint impulses — clone(), equals()
AABB Axis-aligned bounding box — clone(), equals(), fromPoints()
Mat23 2×3 affine matrix — clone(), equals(), transform, inverse
Ray Raycasting — clone(), fromSegment(), spatial queries

Shapes

Class Description
Circle Circular shape
Polygon Convex polygon (with Polygon.box(), Polygon.rect(), Polygon.regular())
Capsule Capsule shape (Capsule.create(), Capsule.createVertical())
Shape Base class with material, filter, sensor support

Physics Properties

Class Description
Material Elasticity, friction, density
BodyType STATIC, DYNAMIC, KINEMATIC
InteractionFilter Bit-mask collision/sensor/fluid filtering
FluidProperties Density, viscosity for fluid shapes

Constraints

Class Description
PivotJoint Pin two bodies at a shared point
DistanceJoint Constrain distance between anchors
WeldJoint Fix relative position and angle
AngleJoint Constrain relative angle
MotorJoint Apply angular velocity
LineJoint Slide along a line
PulleyJoint Constrain combined distances

Callbacks

Class Description
InteractionListener Collision/sensor/fluid events
BodyListener Body wake/sleep events
ConstraintListener Constraint events
PreListener Pre-collision filtering
CbType Tag interactors for filtering
CbEvent BEGIN, ONGOING, END, WAKE, SLEEP, BREAK

Utilities

Class Description
NapeList<T> Iterable list with for...of support
MatMN Variable-sized M×N matrix — clone(), equals(), multiply, transpose

Serialization

Full physics state snapshot/restore — suitable for save/load, replay, and multiplayer server↔client synchronization.

import "@newkrok/nape-js";
import { spaceToJSON, spaceFromJSON } from "@newkrok/nape-js/serialization";

// Serialize
const snapshot = spaceToJSON(space);
const json = JSON.stringify(snapshot);

// Restore (e.g. on another machine / after network transfer)
const restored = spaceFromJSON(JSON.parse(json));
restored.step(1 / 60);

The /serialization entry point is tree-shakeable — it does not pull in the engine bootstrap when unused. The snapshot captures bodies, shapes, materials, interaction filters, fluid properties, all constraint types (except UserConstraint), and compounds. Arbiters and broadphase tree state are reconstructed automatically on the first step.

Web Worker

Run physics off the main thread for smooth rendering even with hundreds of bodies.

import "@newkrok/nape-js";
import { PhysicsWorkerManager } from "@newkrok/nape-js/worker";

const mgr = new PhysicsWorkerManager({ gravityY: 600, maxBodies: 256 });
await mgr.init();

const id = mgr.addBody("dynamic", 100, 50, [{ type: "circle", radius: 20 }]);
mgr.start();

// Read transforms on the main thread (zero-copy with SharedArrayBuffer)
function render() {
  const t = mgr.getTransform(id);
  if (t) drawCircle(t.x, t.y, t.rotation);
  requestAnimationFrame(render);
}
render();

Uses SharedArrayBuffer for zero-copy transform sharing when COOP/COEP headers are present, with automatic postMessage fallback otherwise.

Known Issues

  • Zero-friction tunneling — Bodies with zero-friction material and horizontal velocity may tunnel through floors. This affects all shape types (circles, polygons, capsules). Workaround: use small friction values (e.g. 0.01).

Development

npm install
npm run build      # tsup → packages/*/dist/ (ESM + CJS + DTS)
npm test           # vitest — 5275 engine tests + 71 pixi-adapter tests
npm run benchmark  # Performance benchmarks

Releases are automated: see docs/guides/workflow.md for the per-package auto-release pipeline. Run node scripts/ci/release.mjs --dry-run from the repo root to preview what would publish on the next master merge.

License

MIT

About

A fully typed TypeScript 2D physics engine — modernized rewrite of the original Haxe engine.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors