nexus-physics
v1.0.1
Published
High-performance 2D rigid body physics engine — sequential impulse solver, dynamic BVH broadphase, GJK/EPA, joints, force fields, sleeping
Maintainers
Readme
Nexus2D
High-performance 2D rigid body physics engine for games and simulations.
Zero dependencies. Pure JavaScript ES Modules. Works in browsers and Node.js.
Features
- Rigid body dynamics — Dynamic, static, and kinematic bodies with mass, inertia, forces, impulses, and torque
- Shape primitives — Circle, convex polygon, capsule, and edge shapes with automatic mass computation
- Collision detection — SAT (polygon-polygon), analytical (circle-circle, circle-polygon), GJK+EPA (generic convex), capsule tests
- Sequential impulse solver — Normal + tangent (Coulomb friction) impulses, Baumgarte position correction, warm starting
- Dynamic BVH broadphase — O(log n) self-balancing bounding volume hierarchy with fat AABBs and velocity prediction
- Joint constraints — Distance, revolute (pin), prismatic (piston), weld, and spring joints with motors and limits
- Force fields — Gravity point, wind, vortex, explosion, and buoyancy fields
- Island-based sleeping — Automatic body sleeping via contact-graph islands
- Raycasting & queries — Ray, AABB, circle, and point queries with layer/mask filtering
- Sub-stepping — Fixed timestep with accumulator for deterministic simulation
- Debug renderer — Canvas 2D renderer with contacts, normals, velocities, joints, AABBs, trails, and stats HUD
- Material system — Preset materials (steel, rubber, wood, ice, bouncy, etc.) with density, friction, restitution
Install
npm install nexus-physicsQuick Start
import { World, Body, CircleShape, PolygonShape, Vec2 } from 'nexus-physics';
// Create world with gravity
const world = new World({ gravity: new Vec2(0, 980) });
// Add a dynamic circle
const ball = new Body(new CircleShape(20), 400, 100);
world.addBody(ball);
// Add a static floor
const floor = new Body(PolygonShape.box(400, 20), 400, 550, { type: 'static' });
world.addBody(floor);
// Step the simulation
function loop() {
world.step(1 / 60);
requestAnimationFrame(loop);
}
loop();Shapes
import { CircleShape, PolygonShape, CapsuleShape } from 'nexus-physics';
// Circle (radius)
const circle = new CircleShape(25);
// Box (half-width, half-height)
const box = PolygonShape.box(50, 30);
// Regular polygon (sides, radius)
const hexagon = PolygonShape.regularPolygon(6, 40);
// Custom convex polygon (auto-hulled)
const triangle = new PolygonShape([
new Vec2(0, -40), new Vec2(30, 30), new Vec2(-30, 30)
]);
// Capsule (half-height, radius)
const capsule = new CapsuleShape(30, 15);Bodies
import { Body, BodyType, BodyFlags, Materials } from 'nexus-physics';
const body = new Body(shape, x, y, {
type: BodyType.DYNAMIC, // 'dynamic' | 'static' | 'kinematic'
material: Materials.RUBBER, // density, friction, restitution
angle: Math.PI / 4,
velX: 100, velY: 0,
flags: BodyFlags.FIXED_ROTATION | BodyFlags.NO_GRAVITY,
gravityScale: 0.5,
layer: 0x0001, mask: 0xFFFF,
});
body.applyForce(new Vec2(500, 0));
body.applyImpulse(new Vec2(0, -200), body.pos);
body.applyTorque(50);Joints
import { DistanceJoint, RevoluteJoint, SpringJoint, Vec2 } from 'nexus-physics';
// Pin joint
const pin = new RevoluteJoint(bodyA, bodyB, anchorWorldPos, {
enableMotor: true,
motorSpeed: 2.0,
maxMotorTorque: 500,
});
world.addJoint(pin);
// Spring
const spring = new SpringJoint(bodyA, bodyB,
new Vec2(0, 0), new Vec2(0, 0), // local anchors
{ restLength: 100, stiffness: 500, damping: 20 }
);
world.addJoint(spring);Force Fields
import { GravityPoint, WindField, BuoyancyField } from 'nexus-physics';
world.addField(new GravityPoint(400, 300, 50000, 300));
world.addField(new WindField(1, 0, 200, 0.3));
world.addField(new BuoyancyField(400, 1.0, 0.5, 0.5));Events
world.on('collisionStart', (manifold) => {
console.log('Hit!', manifold.bodyA.id, manifold.bodyB.id);
});
world.on('collisionEnd', (manifold) => {
console.log('Separated!', manifold.bodyA.id, manifold.bodyB.id);
});Raycasting & Queries
const hit = world.raycast(origin, direction, maxDistance, {
filter: (body) => body.type !== 'static',
layer: 0x0001,
});
if (hit) console.log(hit.body, hit.point, hit.normal, hit.t);
const bodies = world.queryCircle(center, radius);
const bodies = world.queryAABB(aabb);
const bodies = world.queryPoint(point);Debug Renderer
import { DebugRenderer } from 'nexus-physics';
const renderer = new DebugRenderer(canvas);
renderer.flags.aabbs = true;
renderer.flags.grid = true;
function draw() {
renderer.render(world);
requestAnimationFrame(draw);
}World Configuration
const world = new World({
gravity: new Vec2(0, 980),
subSteps: 4,
velocityIter: 10,
positionIter: 4,
warmStarting: true,
sleepEnabled: true,
sleepTime: 0.5,
linearDamping: 0.003,
angularDamping: 0.003,
});License
MIT
