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

@nickpaustian/velora

v0.1.0

Published

Small physics for interactive scenes: rigid bodies with forces, gravity and drag, box, capsule and mesh colliders with layers and triggers, swept OBB collision with time of impact, raycasts; standalone, with a three.js adapter

Readme

@nickpaustian/velora

Small physics for interactive scenes: bodies with mass that forces push and gravity pulls, slowed by drag; box, capsule and mesh colliders on layers, solid or triggers; swept oriented-box collision that stops a body at the moment of contact and slides it along the surface; raycasts. Standalone, with its own vectors, quaternions, boxes and rays, and a three.js adapter beside it. Depends on @nickpaustian/signal only.

import { World, RigidBody, CapsuleCollider, BoxCollider } from "@nickpaustian/velora"

const world = new World({ gravity: 9.8, floor: 0 })
world.add(new BoxCollider({ width: 10, height: 1, depth: 10, position: { x: 0, y: -0.5, z: 0 }, layers: 1 }))

const player = new CharacterController({ position: { x: 0, y: 1, z: 0 }, collidesWith: 1 })
world.add(player.body)

function frame(dt: number) {
  player.setIntentFromYaw(input.forward, input.strafe, camera.yaw, input.sprint)
  if (input.jumpPressed) player.jump()
  player.update(dt)
  world.step(dt)
  camera.position.copy(player.position)
}

Every position, rotation and direction is a plain { x, y, z } or { x, y, z, w }, so a three.js vector or quaternion passes straight in, and the library's own come back with the same fields.

World

new World({ gravity, drag, floor, fixedStep, maxSubSteps }): add and remove bodies and colliders (a body brings its collider), step(dt) in seconds, raycast(origin, direction, { maxDistance, layers, triggers }) returning hits nearest first, each with the distance, the point and the collider. World.default is a world made on first use for hosts with one scene.

Beside raycast, the world answers overlaps and casts. overlapSphere(center, radius), overlapBox(center, halfExtents, rotation) and overlapCapsule(a, b, radius) return every collider the shape intersects right now: everything in a blast radius, what is in front of the player, whether a spawn point is clear. sphereCast(origin, radius, direction, distance), boxCast and capsuleCast sweep the shape and return the first collider it would touch, with the fraction and distance along the sweep, the point and the surface normal, or null: a camera boom that pulls in before the camera clips a wall, whether the player fits through a gap, a projectile with width. A ray is a cast with no thickness; the thickness is the point. All take { layers, triggers, ignore }.

step(dt) runs the simulation in fixed steps (fixedStep, 1/120 s by default) and keeps the remainder for next time, so behaviour is identical at 60 and 120 Hz and a long frame cannot make a body skip a wall; at most maxSubSteps (8) run per call and time beyond that is dropped. world.alpha is how far into the next step the frame is, and body.interpolated(world.alpha) is the position to draw, blended from the last step to this one, so motion is smooth between steps. stepOnce(dt) runs one step of exactly dt, and fixedStep: 0 makes every step one.

Bodies

new RigidBody({ position, mass, drag, angularDrag, useGravity, gravity, floor, collider, restitution, friction, stands, rolls, kinematic }). applyForce accumulates for the step, applyImpulse changes the momentum at once, velocity and angularVelocity are yours to set, isGrounded says whether something below took the weight, clampSpeed and clampHorizontalSpeed cap it. drag, gravity and floor fall back to the world's when unset; the floor is a safety net that stops a fall with no bounce, friction or rolling, so give a scene a real floor collider for those. signals.onMove fires each step the body moved, with the delta.

Contacts answer with a velocity response: the part of the velocity into a surface is taken away, or reversed by the restitution when the body hits fast enough to bounce (above bounceThreshold, 1 m/s, so a resting body stays put), and friction takes from the part along the surface up to the coefficient times the normal impulse, so a sliding body slows at friction × gravity and a ball on a slope rolls off only when the slope beats its grip. A contact takes the larger restitution and the geometric mean of the frictions of its two sides; colliders carry the same two numbers for static surfaces. Two bodies share the impulse by mass, so a character pushes a crate and a crate shoves a lighter one. stands makes a character-like body instead: on ground its vertical velocity is dropped and contacts do not reshape its velocity, so it holds a slope and keeps the speed it was given (the character controller sets it); it still shoves the bodies it walks into, as if infinitely heavy. rolls spins a sphere to match its motion on the ground.

With a collider the body is stopped by solid colliders it can collide with, at the moment of contact, and the rest of the move slides along the surface. It stands on anything whose contact normal is within slopeLimit degrees of up (60 by default) and slides off anything steeper. A grounded body's walk follows its ground plane, so it goes down a slope as smoothly as up it, and when a step leaves it just above ground (the far side of a crest, say) it snaps down to ground within snapDistance (0.3) rather than falling; a jump, which moves it upward, is never snapped. A capsule brushing a ledge's edge with its centre past the edge is perched, not grounded: ground has to be found under the centre, so it slides off rather than standing on the corner. With a stepHeight it walks up ledges no taller than that (ledges that stay put: a ball or crate in the way is pushed, not climbed): a blocked move is tried again from stepHeight up and settled back down, and a capsule is carried at least its radius onto the ledge, since a rounded bottom cannot stand on an edge. The triggers it overlaps fire onTriggerEnter, onTriggerStay and onTriggerExit on both colliders' signals, and the solids it meets fire onCollisionEnter, onCollisionStay and onCollisionExit, each with the other collider, the contact normal (toward the collider the signal is on) and, on enter, the speed the two were closing at: a landing thud, a crate hit. Each body reports on its own collider the contacts it moved into and the ones other bodies moved into it; a static or kinematic collider is told by whatever hits it. So every contact is reported once on each side, a body that is hit learning of it on its next step.

Kinematic bodies

A body with kinematic: true is moved by code, not physics: a platform, a lift, a door. It ignores gravity and contacts and goes exactly where its velocity says each step, or where moveTo(position) says (reaching it in the next step at whatever speed that takes), reports onMove, and is infinitely heavy to everything else: a body it moves into is pushed, and a body standing on it is carried along, sideways or up, until it walks or falls off (standingOn names the platform). Kinematic bodies step before the others each step. A kinematic body passing through a trigger does not report it, and a rotating platform does not yet carry its rider round.

Character controller

new CharacterController(options) is a capsule body with the feel done: setIntent(x, z, sprint) or setIntentFromYaw(forward, strafe, yaw, sprint) each frame says where to go, jump() when the key goes down, and update(dt) before the world steps. Velocity eases toward the intent at acceleration on the ground and acceleration × airControl in the air, and away from it at deceleration; speed and sprintSpeed are the targets. A jump pressed within jumpBuffer seconds before landing still happens on landing, and one pressed within coyoteTime of walking off an edge still happens, but never twice in the air. stepHeight (0.35) and slopeLimit (50°) go to the body. signals.onJump and signals.onLand (with the landing speed) fire; position, velocity and isGrounded read through; teleport moves it.

Colliders

Each has a position and rotation (set with setPosition and setRotation, which takes a quaternion or Euler angles), enabled, trigger, layers it is on and collidesWith (empty means everything; it applies to triggers too), restitution and friction for its surface, and an owner: whatever it stands for in your app, typed through Collider<Owner>, back on every hit and contact. bounds is the world axis-aligned box, obb the oriented one; both are cached and returned by reference.

  • BoxCollider: width, height, depth.
  • CapsuleCollider: radius and height end to end, standing on y. It collides as a true capsule, a segment with a radius, so it slides round corners and over edges rather than catching on them; rays hit the box around it.
  • SphereCollider: a radius. A capsule with no length, so it collides exactly with everything a capsule does, and rays hit it exactly.
  • MeshCollider: triangles as flat positions and optional indices, held static: a scanned room, a terrain. A bounding volume hierarchy is built once over it, and rays and bodies alike test only the triangles near them. A capsule or sphere body collides with the triangles exactly (its segment's nearest point to each), a box by a separating-axis test against each; the deepest contact wins. trianglesNear(box, visit) and worldTriangle(i, a, b, c) expose the tree for queries of your own.

Collision starts with the world's spatial hash, a grid of cells (cellSize, 2 by default: about the size of a typical collider) that a body asks for what is near its move, so its tests scale with what is nearby rather than with the whole world; colliders are re-placed when they move and anything spanning thousands of cells (a floor) is kept aside and always checked. Then a swept axis-aligned test per candidate, then up to three passes of a narrow phase by shape (clipping against one surface can push a move back into another, as in a corner): a separating-axis test between oriented boxes, the nearest points of a segment and a box for a capsule against a box, and the nearest points of two segments for two capsules, each with a bisection for the time of impact sampled finely enough that a fast body cannot pass through a thin wall (a graze shorter than the smallest half-extent of the two shapes can still slip by; nothing a body stands on or walks into is that thin).

Saving and loading

Everything is plain data. world.toJSON() (or JSON.stringify(world)) writes the settings, every collider and every body as a versioned record; worldFromJSON(record, context) reads it back into a world that simulates identically. Colliders and bodies have an id (given, or made up) so bodies name their colliders and a host can reattach owners. A mesh collider with a name is written as that name and loaded through context.meshes(name), so a terrain's triangles stay with the asset rather than in the file; an unnamed one is written with its triangles. colliderFromJSON, bodyFromJSON and characterFromJSON (a character is its body, its capsule and its tuning) load the parts on their own. Velocities are saved, so a scene saved mid-motion resumes.

Maths

Vec3, Quat (with axisRotation to keep one axis of a rotation, a camera's yaw say), Box3, OBB with separate and timeOfImpact, Ray with triangle, box and oriented-box intersections, the closest-point routines for segments, boxes and triangles, a box-triangle separating-axis test, and BVH over any triangle source, all exported, all allocation-free in the hot paths. contact(a, b, offset) and timeOfImpactBetween(a, movement, b) work on any two colliders.

three.js

import { DebugShape, syncObject, trianglesOf } from "@nickpaustian/velora/three" (three is an optional peer dependency). new DebugShape(collider, { colour }) is a wireframe of the collider's shape to add to a scene; call sync() after moving it. syncObject(object, body) puts a body's position and rotation on a camera or mesh. trianglesOf(geometry) makes a MeshCollider's triangles from a BufferGeometry. DebugLines draws fresh each frame for debugging: clear(), then body(body) for a body's contact normals and velocity, cast(origin, direction, distance, hit), ray(world, origin, direction), point, normal and line, then commit(); add it to the scene once. body.contacts is the map of colliders touched last step to the normal toward the body, for drawing of your own.

From the old Velora

VeloraEnvironment._ and getInstance() return World.default, and update(ms) still works, all deprecated. registerBody and registerCollider are add; parentNode is owner; isVisualised is a DebugShape you add to the scene; the first-person controller is gone (Catalyst's prefab is the live one). Signals come from @nickpaustian/signal.