@ferret/physics
Layers, triggers and raycasts; the rigid-body kernels (light and Havok); the capability host with scoped configuration; budgets and tiers.
Read the guide: how to use it →
Classes
Collider class
class Collider extends Component<ColliderParams>Anything on a layer that can be overlapped: triggers, interactables, characters. Static level geometry uses markStatic instead.
shape: ShapeKind | |
layerBit: number | |
maskBits: number | |
physics: PhysicsService | Set by the physics service when the component is attached. |
onAttach() | |
onDetach() | |
get isTrigger() | |
center(out = new BABYLON.Vector3()): BABYLON.Vector3 |
packages/physics/src/physics.ts:27
CollisionLayers class
class CollisionLayersUE-style object channels: every collider is on one layer and tests against a mask of layers.
constructor(names: string[] = []) | |
define(name: string): number | |
bit(name: string): number | |
mask(...names: string[]): number | A mask from layer names; '*' = all layers. |
names(): string[] | |
namesOf(mask: number): string[] |
packages/physics/src/layers.ts:2
Governor class
class GovernorWatches the world step and gives up detail in the order of rules when it stays over budget, then takes it back when there is
room again. It acts on the numbers the capabilities already read and on switching capabilities off; the events say what happened
(physics:degrade { level, rule, meanMs, budgetMs }), so a game can tell the player or log it.
readonly budget: TierBudget | |
readonly rules: DegradeRule[] | |
level: number | |
constructor(private host: PhysicsHost, readonly opts: GovernorOptions = {}) | |
observe(ms: number) | Called with the time of each step. |
packages/physics/src/budget.ts:57
HavokKernel class
class HavokKernel implements PhysicsKernelThe rigid-body kernel on Havok's own API (no Babylon scene needed, so it runs headless and in tests): bodies of any shape that move,
turn with their full inertia, stack and rest; joints that report their force and break; swept collision. The game loads the WASM
(await HavokPhysics()) and hands the module in, so a game that does not want rigid bodies does not carry it.
What Havok does not do the same way as the light kernel: contacts cannot be changed before they are solved (caps.contactHooks is
false: friction and restitution come from the materials, combined by the library's rules; a pair table is not used), and there is
no rolling resistance (a capability adds it as a torque).
readonly name: "havok" | |
readonly caps: KernelCaps | |
gravity: Vec3 | |
readonly materials: MaterialLibrary | |
substeps: number | |
readonly bodies: Body[] | |
readonly constraints: Constraint[] | |
readonly world: HP_WorldId | |
constructor(readonly hk: HavokPhysicsWithBindings, o: HavokKernelOptions = {}) | |
dispose() | Free the Havok world and everything in it. |
addBody(def: BodyDef): Body | |
removeBody(b: Body) | |
setMassProperties(b: Body, mass: number, centreOfMass: Vec3, inertia: Vec3) | Mass, centre of mass (body space) and principal inertia set from outside (a vessel whose tanks fill); where the kernel can. |
setSurface(b: Body, sf: { friction: number; kineticFriction: number; restitution: number }) | A body's own friction and restitution, over its material's: how a kernel without contact hooks takes a capability's dials. |
centreOf(b: Body): Vec3 | World position of the body's centre of mass. |
applyForce(b: Body, f: Vec3, at?: Vec3) | |
applyImpulse(b: Body, j: Vec3, at?: Vec3) | |
applyTorque(b: Body, t: Vec3) | |
setVelocity(b: Body, v: Vec3) | |
setPosition(b: Body, p: Vec3) | |
sleep(b: Body) | |
wake(b: Body) | |
addForceField(fn: (b: Body, h: number) => Vec3 | FieldForce | null) | A force that depends on where a body is (gravity, drag, buoyancy): evaluated at the position of every sub-step, so a fast or orbiting body gets the force it feels where it is, not where it was at the start of the step. Returns newtons, or null for none. |
beforeSolve(_cb: (c: Contact) => void) | Havok solves contacts inside its step: a hook is accepted and never called (caps.contactHooks is false). |
onContact(cb: (e: ContactEvent) => void) | Called after the step with what each contact did. |
addConstraint(def: ConstraintDef): Constraint | |
removeConstraint(c: Constraint) | |
onConstraintBreak(cb: (c: Constraint) => void) | Called after the step for each constraint whose force passed its limit; the constraint is already removed. |
step(dt: number): void | |
rayCast(origin: Vec3, dir: Vec3, maxDistance: number): KernelRayHit | null |
packages/physics/src/havok-kernel.ts:36
LightKernel class
class LightKernel implements PhysicsKernelA small deterministic rigid-body kernel: spheres that move, roll, slide and bounce against static planes, boxes and spheres and
against each other. No stacking of boxes and no joints; for projectiles, balls, debris, rolling and sliding tests, and as the
reference the capabilities are checked against. The same PhysicsKernel surface as a heavier backend, so a capability does not know.
readonly name: "light" | |
readonly caps: KernelCaps | |
gravity: Vec3 | |
readonly materials: MaterialLibrary | |
substeps: number | |
maxSubsteps: number | |
iterations: number | |
restitutionThreshold: number | |
readonly bodies: Body[] | |
readonly constraints: Constraint[] | |
constructor(o: LightKernelOptions = {}) | |
addBody(def: BodyDef): Body | |
removeBody(b: Body) | |
addConstraint(def: ConstraintDef): Constraint | |
removeConstraint(c: Constraint) | |
onConstraintBreak(cb: (c: Constraint) => void) | Called after the step for each constraint whose force passed its limit; the constraint is already removed. |
applyForce(b: Body, f: Vec3, at?: Vec3) | |
applyImpulse(b: Body, j: Vec3, at?: Vec3) | |
applyTorque(b: Body, t: Vec3) | |
setVelocity(b: Body, v: Vec3) | |
setPosition(b: Body, p: Vec3) | |
sleep(b: Body) | |
wake(b: Body) | |
addForceField(fn: (b: Body, h: number) => Vec3 | FieldForce | null) | A force that depends on where a body is (gravity, drag, buoyancy): evaluated at the position of every sub-step, so a fast or orbiting body gets the force it feels where it is, not where it was at the start of the step. Returns newtons, or null for none. |
beforeSolve(cb: (c: Contact) => void) | Called for every contact before it is solved, so a capability can change friction or restitution. |
onContact(cb: (e: ContactEvent) => void) | Called after the step with what each contact did. |
step(dt: number): void | |
rayCast(origin: Vec3, dir: Vec3, maxDistance: number): KernelRayHit | null |
packages/physics/src/light-kernel.ts:29
MaterialLibrary class
class MaterialLibrarycombine: CombineRules | |
add(name: string, m: PhysicsMaterial): this | |
get(name: string | undefined): PhysicsMaterial | undefined | |
has(name: string) | |
names() | |
load(table: Record<string, PhysicsMaterial>): this | Load {name: material}; throws with every problem listed when the table is invalid. |
setPair(a: string, b: string, values: PhysicsMaterial): this | The values for one pair of materials that override the combine rule (ice on rubber is not the mean of the two). |
property(a: string | undefined, b: string | undefined, key: string): number | undefined | One property of a pair: the pair table first, then the combine rule; a value only one side has is used as it is. |
contact(a?: string, b?: string) | What a contact between two materials uses. |
packages/physics/src/materials.ts:57
PhysicsHost class
class PhysicsHost implements ServiceThe home of the capabilities: the kernel, the materials, the registry and the fixed order of the step. A game registers the capabilities it wants and switches them on (in code, in the manifest, or by command); nothing realistic runs otherwise.
readonly name: "physics.host" | |
readonly kernel: PhysicsKernel | |
readonly materials: MaterialLibrary | |
readonly bodies: Set<RigidBody> | |
readonly fields: PhysicsFields | Fields that other code (a character, a vessel) may read when the capability behind them is on; each is set and cleared by its capability. |
readonly capabilities: Map<string, Capability> | |
world: World | |
zoneAt(p: Vec3): string | null | Which zone a point is in (none when the world has no ZoneService). |
lastStepMs: number | Seconds the last step's physics took (ms), for the budget view. |
constructor(o: PhysicsHostOptions = {}) | |
init(world: World) | |
dispose() | |
register(cap: Capability): this | |
config(name: string): ScopedConfig | |
isEnabled(name: string) | |
enabled(): string[] | |
enable(name: string, values?: Record<string, unknown>): this | |
disable(name: string): this | |
configure(c: PhysicsConfig, scope: 'world' | 'level' = 'level') | Apply a physics block (from a level manifest or a game): materials, pair rules, combine rules, then the capabilities with their values. |
readonly fractures: Map<string, LevelFracture> | Pre-fractured bodies of the level, by the name of the intact body's node (read by the strength capability when that body breaks). |
governor: Governor | null | Gives up detail when the step runs over its tier's budget (see Governor); none until a game sets one. |
beginLevel(c: PhysicsConfig): string[] | A level's physics block: applied now, undone by endLevel (the capabilities it switched on go off, its scoped values go).
Constraints and fractures name nodes, so the level's bodies must exist already. Returns what could not be linked. |
endLevel() | |
fixedUpdate(dt: number) | Called before the components' fixed update, at the fixed rate. |
packages/physics/src/host.ts:210
PhysicsService class
class PhysicsService implements ServiceCollision layers, trigger volumes with enter/exit events, raycasts and overlap queries. Static level geometry is Babylon meshes with checkCollisions; characters move against it (see
readonly name: "physics" | |
readonly layers: CollisionLayers | |
readonly colliders: Set<Collider> | |
constructor(opts: PhysicsOptions = {}) | |
init(world: World) | |
markStatic(root: BABYLON.TransformNode | BABYLON.AbstractMesh, layer = 'World') | Make a node's meshes static world geometry: blocking for characters and hit by raycasts. |
raycast(origin: BABYLON.Vector3, dir: BABYLON.Vector3, maxDistance: number, opts: { mask?: number; ignore?: (m: BABYLON.AbstractMesh) => boolean } = {}): RayHit | null | First hit of a ray against static geometry (and optionally entity colliders' meshes) on the masked layers. |
overlapSphere(center: BABYLON.Vector3, radius: number, mask = -1): Collider[] | Colliders whose volume overlaps a sphere. |
dropColliderPairs(c: Collider) | |
fixedUpdate() | Called before the components' fixed update, at the fixed rate. |
occupants(trigger: Collider): Entity[] | Who is inside a trigger right now. |
dispose() |
packages/physics/src/physics.ts:58
RigidBody class
class RigidBody extends Component<RigidBodyParams>A node that the physics kernel moves: the node's position and rotation follow the body after every step.
host: PhysicsHost | |
body: Body | |
onAttach() | |
onDetach() | |
get velocity(): Vec3 |
packages/physics/src/host.ts:103
ScopedConfig class
class ScopedConfigA capability's values at four scopes; the narrowest that has the key wins: entity, zone, level, world, then the defaults.
constructor(private host: PhysicsHost, readonly defaults: Record<string, unknown> = {}) | |
set(scope: 'world' | 'level', key: string, value: unknown): void | |
set(scope: 'zone' | 'entity', key: string, value: unknown, id: string): void | |
set(scope: Scope, key: string, value: unknown, id?: string) | |
assign(scope: 'world' | 'level', values: Record<string, unknown>) | |
clearLevel() | |
where(key: string, ctx: ScopeContext = {}): Scope | 'default' | undefined | Which layer a value comes from for this context: the narrowest scope that has the key, or default / undefined when none has it. |
get<T = number>(key: string, ctx: ScopeContext = {}): T |
packages/physics/src/host.ts:42
Functions
add function
function add: (a: Vec3, b: Vec3) => Vec3packages/physics/src/vec.ts:7
addTo function
function addTo: (a: Vec3, b: Vec3, s?: number) => voidpackages/physics/src/vec.ts:15
boundingRadius function
function boundingRadius(s: Shape): numberRadius of the sphere around the body's origin that holds the shape.
packages/physics/src/shapes.ts:103
bounds function
function bounds(s: Shape): [Vec3, Vec3]Axis-aligned bounds in body space.
packages/physics/src/shapes.ts:36
combineValues function
function combineValues(rule: CombineRule, a: number, b: number): numberpackages/physics/src/materials.ts:47
contactScope function
function contactScope(a: Body, b: Body, point: Vec3): ScopeContextWhere to look up a capability's scoped values for a contact: the dynamic body's entity (else the other one), and the contact point (so a zone applies).
packages/physics/src/host.ts:434
copy function
function copy: (a: Vec3) => Vec3packages/physics/src/vec.ts:16
cross function
function cross: (a: Vec3, b: Vec3) => Vec3packages/physics/src/vec.ts:11
defaultRules function
function defaultRules: (t: TierBudget) => DegradeRule[]packages/physics/src/budget.ts:34
dot function
function dot: (a: Vec3, b: Vec3) => numberpackages/physics/src/vec.ts:10
entityOf function
function entityOf(b: Body): Entity | undefinedThe entity a body belongs to (the RigidBody entity), when it has one.
packages/physics/src/host.ts:428
len function
function len: (a: Vec3) => numberpackages/physics/src/vec.ts:12
len2 function
function len2: (a: Vec3) => numberpackages/physics/src/vec.ts:13
localSdf function
function localSdf(s: Shape): (x: number, y: number, z: number) => numberGeometry of the body shapes in body space, for capabilities that need more than a radius: a signed distance (negative inside), the volume, the bounding box, and a lattice of sample points that stand for the volume (buoyancy, drag, heat, a liquid's walls).
packages/physics/src/shapes.ts:8
lowestBelow function
function lowestBelow(s: Shape, q: [number, number, number, number]): numberHeight of the shape's lowest point below the body's origin, the body turned by q (the radius, for a sphere), m.
packages/physics/src/shapes.ts:145
norm function
function norm: (a: Vec3) => Vec3packages/physics/src/vec.ts:14
overlapsSphere function
function overlapsSphere(c: Collider, center: BABYLON.Vector3, radius: number): booleanIs a world-space sphere inside the collider's volume (box: oriented; sphere; capsule)?
packages/physics/src/physics.ts:160
overlapsTrigger function
function overlapsTrigger(t: Collider, o: Collider): booleanDoes collider o overlap the trigger volume t? A capsule is tested as a swept sphere along its axis (several samples).
packages/physics/src/physics.ts:174
projectedArea function
function projectedArea(s: Shape, q: [number, number, number, number], dir: Vec3): numberArea the shape shows to a direction (world dir, the body turned by q), m²: exact for a sphere and a box, from the volume otherwise.
packages/physics/src/shapes.ts:110
qFromAxisAngle function
function qFromAxisAngle(axis: Vec3, angle: number): Quatpackages/physics/src/vec.ts:25
qIdentity function
function qIdentity: () => Quatpackages/physics/src/vec.ts:18
qIntegrate function
function qIntegrate(q: Quat, w: Vec3, dt: number): QuatIntegrate an orientation by an angular velocity over dt (exact for constant w).
packages/physics/src/vec.ts:38
qInverse function
function qInverse: (q: Quat) => Quatpackages/physics/src/vec.ts:24
qMul function
function qMul(a: Quat, b: Quat): Quatpackages/physics/src/vec.ts:29
qRotate function
function qRotate(q: Quat, v: Vec3): Vec3packages/physics/src/vec.ts:19
sampleShape function
function sampleShape(s: Shape, n = 4): ShapeSamplesn cells along each axis of the bounds, the ones whose centre is inside; each carries an equal share of the volume.
packages/physics/src/shapes.ts:97
scale function
function scale: (a: Vec3, s: number) => Vec3packages/physics/src/vec.ts:9
scopedSurface function
function scopedSurface(host: PhysicsHost, b: Body): { friction: number; kineticFriction: number; restitution: number }A body's friction and restitution where it is now: its material's, times the friction capability's scale and the impact capability's
scale at its scope when they are on. What a kernel without contact hooks is given through setSurface.
packages/physics/src/host.ts:443
seededRng function
function seededRng(seed: number): () => numberA seeded generator (mulberry32): the same seed gives the same sequence on every machine.
packages/physics/src/vec.ts:48
shapeVolume function
function shapeVolume(s: Shape): numberExact volume where the shape has a formula; otherwise from a fine lattice.
packages/physics/src/shapes.ts:77
splitShape function
function splitShape(s: Shape, n: number): Piece[]Cut a shape into about n box pieces (body space): the bounds cut along their longest edges until there are n cells, the cells whose centre lies outside the shape dropped and their volume shared among the rest, so the pieces hold the whole volume. A sphere is cut the same way.
packages/physics/src/shapes.ts:126
sub function
function sub: (a: Vec3, b: Vec3) => Vec3packages/physics/src/vec.ts:8
syncSurfaces function
function syncSurfaces(host: PhysicsHost, seen: Map<Body, string>)Give every body whose scoped surface changed its new surface (kernels without contact hooks); seen keeps what each one has.
packages/physics/src/host.ts:451
v3 function
function v3: (x?: number, y?: number, z?: number) => Vec3packages/physics/src/vec.ts:6
validateMaterials function
function validateMaterials(table: unknown, base = 'materials'): Issue[]Check a materials table: numbers only, inside sane ranges. Returns the problems (empty when fine).
packages/physics/src/materials.ts:33
validatePhysicsConfig function
function validatePhysicsConfig(c: unknown, known: string[] = []): Issue[]packages/physics/src/host.ts:159
volumeBelow function
function volumeBelow(s: Shape, p: Vec3, q: [number, number, number, number], y: number): numberVolume of the shape below a horizontal surface at height y, the body at p turned by q: by its sample cells (exact for a sphere).
packages/physics/src/shapes.ts:156
Interfaces
Body interface
interface Bodyreadonly id: number | |
readonly kind: BodyKind | |
readonly shape: Shape | |
mass: number | |
invMass: number | |
invInertia: number | |
inertia?: Vec3 | principal moments of inertia, kg·m², where the kernel knows them (a box, a hull); a sphere's three are equal |
position: Vec3 | |
orientation: Quat | |
velocity: Vec3 | |
angularVelocity: Vec3 | |
force: Vec3 | |
torque: Vec3 | |
material?: string | |
linearDamping: number | |
angularDamping: number | |
gravityScale: number | |
sleeping: boolean | |
userData?: unknown | |
removed: boolean | |
exempt?: string[] | capabilities that leave this body alone (a vessel computes its own weight and buoyancy: ['gravity', 'media']) |
packages/physics/src/kernel.ts:39
BodyDef interface
interface BodyDefshape: Shape | |
kind?: BodyKind | |
mass?: number | |
position?: Vec3 | |
orientation?: Quat | |
velocity?: Vec3 | |
angularVelocity?: Vec3 | |
material?: string | |
linearDamping?: number | |
angularDamping?: number | |
gravityScale?: number | |
lockRotation?: boolean | No spin: the sphere slides like a block (a crate, a puck) instead of rolling. |
noCollide?: boolean | Touches nothing: other bodies pass through it (a pivot pin that only anchors joints). Joints still hold it. A rigid-body kernel only. |
userData?: unknown |
packages/physics/src/kernel.ts:20
Capability interface
interface Capabilityreadonly name: string | |
readonly phase: Phase | |
readonly order?: number | Position among capabilities of the same phase (lower first); ties go by name, never by registration. |
readonly cost?: string | What it costs, in words, for the inspector and the budget notes. |
readonly defaults?: Record<string, unknown> | Values at every scope unless overridden. |
readonly needsBodies?: boolean | Needs rigid bodies from the kernel; it refuses to switch on when the kernel has none. |
readonly requires?: string[] | Names of capabilities that must be on first. |
load?(host: PhysicsHost, value: unknown[], scope: 'world' | 'level'): void | A list in the level's physics block under this capability's name (actuators): handed over when the block is applied. |
loadBindings?(host: PhysicsHost, value: unknown[]): void | The level's physics.bindings list (actuators): what drives its commands from input, interactables, zones and events. Dropped with the level. |
unloadBindings?(): void | |
enable?(host: PhysicsHost): void | |
disable?(host: PhysicsHost): void | |
step?(dt: number, host: PhysicsHost, contacts: readonly ContactEvent[]): void | Phase 'field': add forces for this step. Phases 'strength' and 'stretch': act on what the step did. |
packages/physics/src/host.ts:13
ColliderParams interface
interface ColliderParamsshape?: ShapeKind | |
size?: [number, number, number] | box: full size (x, y, z) |
radius?: number | sphere / capsule radius |
height?: number | capsule total height (feet to head) |
offset?: [number, number, number] | centre offset from the entity's origin |
layer?: string | |
mask?: string[] | layers this collider overlaps with (triggers) or collides with |
trigger?: boolean | a trigger fires enter/exit events instead of blocking |
packages/physics/src/physics.ts:6
CombineRules interface
interface CombineRulesfriction: CombineRule | |
restitution: CombineRule | |
rollingFriction: CombineRule | |
[property: string]: CombineRule |
packages/physics/src/materials.ts:20
Constraint interface
interface Constraintreadonly id: number | |
type: ConstraintType | |
readonly a: Body | |
readonly b: Body | |
length: number | |
stiffness: number | |
damping: number | |
maxForce: number | |
userData?: unknown | |
force: number | force over the last step, N: positive in tension (it pulls the two bodies together), negative in compression |
stretch: number | present distance less the length, m |
broken: boolean | |
anchor?: Vec3 | |
axis?: Vec3 |
packages/physics/src/kernel.ts:160
ConstraintDef interface
interface ConstraintDeftype: ConstraintType | |
a: Body | |
b: Body | |
length?: number | the distance (rod), the longest distance (rope) or the rest length (spring); the present distance when missing |
stiffness?: number | N/m (spring) |
damping?: number | N·s/m (spring) |
maxForce?: number | the constraint breaks when the force on it (N) passes this; never when missing |
anchor?: Vec3 | ball, hinge, fixed: the shared point in world space (the middle between the bodies when missing) |
axis?: Vec3 | hinge: the axis in world space (up when missing) |
limits?: [number, number] | hinge: the angle from the pose it was made in, radians, from the lower to the upper limit; free when missing |
userData?: unknown |
packages/physics/src/kernel.ts:140
Contact interface
interface ContactOne contact between two bodies during a step. normal points from b toward a.
a: Body | |
b: Body | |
point: Vec3 | |
normal: Vec3 | |
depth: number | |
friction: number | Mutable before the solve: a hook may change what this contact uses. |
kineticFriction: number | |
rollingFriction: number | |
pivotFriction: number | |
restitution: number | |
restitutionThreshold: number | Speed below which the bounce is a rest. |
readonly closing: number | Closing speed along the normal when the contact is made (m/s, positive = approaching); read-only for hooks. |
anisotropy?: { axis: Vec3; across: number } | Friction that differs along a direction (skates, a rail): the coefficient across axis is across times the one along it. |
sticky?: boolean | A capability's request that the two bodies leave this contact as one (a perfectly inelastic hit). |
packages/physics/src/kernel.ts:66
ContactEvent interface
interface ContactEventWhat happened at one contact over a step, delivered after it.
a: Body | |
b: Body | |
point: Vec3 | |
normal: Vec3 | |
impulse: number | Total normal impulse over the step (N·s). |
speed: number | Largest closing speed along the normal during the step (m/s). |
slide: number | Sliding speed along the surface at the largest impact (m/s). |
restitution: number | The restitution the contact was solved with, and whether a capability asked the bodies to stick. |
sticky: boolean |
packages/physics/src/kernel.ts:89
DegradeRule interface
interface DegradeRuleOne step down: values to set at world scope (capability.key = value; a number may be a share of the tier's own: of) and capabilities to switch off.
name: string | |
set?: { capability: string; key: string; value: number }[] | |
disable?: string[] |
packages/physics/src/budget.ts:32
FieldForce interface
interface FieldForceWhat a force field may return: a force, a torque, or both.
packages/physics/src/kernel.ts:108
GovernorOptions interface
interface GovernorOptionstier?: Tier | |
stepMs?: number | override the tier's step budget, ms |
rules?: DegradeRule[] | |
window?: number | steps averaged before a decision (default 30) |
down?: number | windows over budget in a row before stepping down (default 2) and windows well under it before stepping back up (default 4) |
up?: number |
packages/physics/src/budget.ts:40
HavokKernelOptions interface
interface HavokKernelOptionsmaterials?: MaterialLibrary | |
gravity?: Vec3 | |
substeps?: number | Havok steps per world step; force fields are evaluated before each (1 = once a step) |
packages/physics/src/havok-kernel.ts:6
Issue interface
interface Issuepath: string | |
message: string |
packages/physics/src/materials.ts:30
KernelCaps interface
interface KernelCapsfriction: boolean | Static and kinetic friction, restitution with a threshold and combine rules from materials. |
restitutionCombine: boolean | |
rollingFriction: boolean | |
contactImpulses: boolean | |
constraints: boolean | |
breakableConstraints: boolean | |
ccd: 'none' | 'substep' | 'native' | 'substep' = small steps scaled by speed; 'native' = swept collision. |
shapes: string[] | |
contactHooks?: boolean | beforeSolve can change a contact before it is solved; false: hooks see nothing and per-pair changes go through materials |
rigidBodies?: boolean | bodies of any shape move, rotate with a full inertia tensor and stack |
joints?: boolean | ball, hinge and fixed constraints |
packages/physics/src/kernel.ts:110
KernelRayHit interface
interface KernelRayHitpackages/physics/src/kernel.ts:105
LevelConstraint interface
interface LevelConstraintid?: string | |
type: 'rod' | 'rope' | 'spring' | 'ball' | 'hinge' | 'fixed' | |
a: string | |
b: string | |
length?: number | |
stiffness?: number | |
damping?: number | |
maxForce?: number | |
anchor?: Vec3 | |
axis?: Vec3 | |
limits?: [number, number] |
packages/physics/src/host.ts:154
LevelFracture interface
interface LevelFracturenode: string | |
pieces: string[] | |
material?: string | |
toughness?: number |
packages/physics/src/host.ts:155
LightKernelOptions interface
interface LightKernelOptionsmaterials?: MaterialLibrary | |
gravity?: Vec3 | |
substeps?: number | Smallest number of sub-steps per step; fast bodies get more (up to maxSubsteps) so they do not tunnel. |
maxSubsteps?: number | |
iterations?: number | |
restitutionThreshold?: number | Closing speed below which a bounce is a rest (m/s). |
packages/physics/src/light-kernel.ts:5
MediumSample interface
interface MediumSamplename: string | |
density: number | |
viscosity: number | |
flow: Vec3 |
packages/physics/src/host.ts:136
PhysicsConfig interface
interface PhysicsConfigWhat a physics block of a level or a game may hold.
capabilities?: string[] | |
materials?: Record<string, PhysicsMaterial> | |
pairs?: Array<{ a: string; b: string; values: PhysicsMaterial }> | |
combine?: Record<string, CombineRule> | |
gravity?: { vector?: Vec3 } | |
constraints?: LevelConstraint[] | links between two bodies of the level, by the names of their nodes (a Physics Constraint of Unreal where the kernel has an equivalent) |
fractured?: LevelFracture[] | bodies authored in pieces: when the intact one breaks, its pieces (nodes of the level) take over |
[capability: string]: unknown |
packages/physics/src/host.ts:142
PhysicsFields interface
interface PhysicsFieldsRead access to what capabilities provide: gravity at a point (m/s², a vector) and, later, the medium.
gravity?: (p: Vec3, ctx?: ScopeContext) => Vec3 | |
medium?: (p: Vec3, ctx?: ScopeContext) => MediumSample | The medium at a point (air by default): density kg/m³, dynamic viscosity Pa·s, the flow velocity m/s (wind, current). |
column?: (p: Vec3, height: number, ctx?: ScopeContext) => { fraction: number; medium: MediumSample | null } | The part of a vertical segment of height standing at p that is inside a dense medium (water, mud), and that medium. |
packages/physics/src/host.ts:129
PhysicsHostOptions interface
interface PhysicsHostOptionskernel?: PhysicsKernel | |
materials?: MaterialLibrary | |
capabilities?: Capability[] |
packages/physics/src/host.ts:139
PhysicsKernel interface
interface PhysicsKernelreadonly name: string | |
readonly caps: KernelCaps | |
gravity: Vec3 | |
readonly bodies: readonly Body[] | |
readonly constraints: readonly Constraint[] | |
step(dt: number): void | |
addBody(def: BodyDef): Body | |
removeBody(b: Body): void | |
addConstraint(def: ConstraintDef): Constraint | |
removeConstraint(c: Constraint): void | |
onConstraintBreak(cb: (c: Constraint) => void): () => void | Called after the step for each constraint whose force passed its limit; the constraint is already removed. |
applyForce(b: Body, f: Vec3, at?: Vec3): void | |
applyImpulse(b: Body, j: Vec3, at?: Vec3): void | |
applyTorque(b: Body, t: Vec3): void | |
setVelocity(b: Body, v: Vec3): void | |
setPosition(b: Body, p: Vec3): void | |
sleep(b: Body): void | |
wake(b: Body): void | |
addForceField(fn: (b: Body, h: number) => Vec3 | FieldForce | null): () => void | A force that depends on where a body is (gravity, drag, buoyancy): evaluated at the position of every sub-step, so a fast or orbiting body gets the force it feels where it is, not where it was at the start of the step. Returns newtons, or null for none. |
beforeSolve(cb: (c: Contact) => void): () => void | Called for every contact before it is solved, so a capability can change friction or restitution. |
onContact(cb: (e: ContactEvent) => void): () => void | Called after the step with what each contact did. |
rayCast(origin: Vec3, dir: Vec3, maxDistance: number): KernelRayHit | null | |
setMassProperties?(b: Body, mass: number, centreOfMass: Vec3, inertia: Vec3): void | Mass, centre of mass (body space) and principal inertia set from outside (a vessel whose tanks fill); where the kernel can. |
setSurface?(b: Body, s: { friction: number; kineticFriction: number; restitution: number }): void | A body's own friction and restitution, over its material's: how a kernel without contact hooks takes a capability's dials. |
packages/physics/src/kernel.ts:179
PhysicsMaterial interface
interface PhysicsMaterialPhysical materials as data. A value that is not given means "this capability does not apply to it", not a default: a body with no strength cannot break. The few values a contact cannot do without (friction, restitution) have library defaults, stated here.
density?: number | |
friction?: number | |
kineticFriction?: number | |
rollingFriction?: number | |
pivotFriction?: number | |
restitution?: number | |
youngModulus?: number | |
yieldStrength?: number | |
tensileStrength?: number | |
toughness?: number | |
[property: string]: number | undefined |
packages/physics/src/materials.ts:5
PhysicsOptions interface
interface PhysicsOptionslayers?: string[] |
packages/physics/src/physics.ts:51
Piece interface
interface Piecepackages/physics/src/shapes.ts:121
RayHit interface
interface RayHitpoint: BABYLON.Vector3 | |
normal: BABYLON.Vector3 | |
distance: number | |
mesh: BABYLON.AbstractMesh | |
entity?: Entity |
packages/physics/src/physics.ts:24
RigidBodyParams interface
interface RigidBodyParamsshape?: 'sphere' | 'box' | 'plane' | 'capsule' | 'cylinder' | |
radius?: number | |
height?: number | capsule and cylinder: the length along y (a capsule's between the centres of its caps) |
geometry?: Shape | any shape the kernel has (a hull, a compound, a mesh): wins over shape |
size?: [number, number, number] | box: full size, like Collider |
normal?: Vec3 | |
offset?: number | |
mass?: number | kg; taken from the material density and the shape when missing |
material?: string | |
kind?: BodyKind | |
linearDamping?: number | |
angularDamping?: number | |
gravityScale?: number | |
velocity?: Vec3 | |
angularVelocity?: Vec3 | |
lockRotation?: boolean | |
rotate?: boolean | Turn the node with the body (a rolling ball); off for a body whose node is a model that should stay upright. |
packages/physics/src/host.ts:81
ScopeContext interface
interface ScopeContextWhere a value is asked from: an entity, or just a place (a zone covers it).
packages/physics/src/host.ts:38
ShapeSamples interface
interface ShapeSamplespoints: Vec3[] | cell centres inside the shape, body space |
cell: Vec3 | the cell's edges along x, y, z, m |
volume: number | the volume each point stands for, m³ (the shape's volume over the count, so the sum is exact where the volume is known) |
packages/physics/src/shapes.ts:67
TierBudget interface
interface TierBudgetstepMs: number | the share of a frame the whole physics step may take, milliseconds |
maxPieces: number | pieces alive at once (the strength capability's maxPieces) |
fluidParticles: number | particles of all liquids, and of all cloths: for the game to give the capabilities that take such a budget |
clothParticles: number | |
flowSources: number | flow sources the level's field takes (FlowField.maxSources) and the cells of flow boxes solved per physics step, all boxes together |
flowBoxCells: number |
packages/physics/src/budget.ts:12
TriggerEvent interface
interface TriggerEventpackages/physics/src/physics.ts:23
Types
BodyKind type
type BodyKind = 'static' | 'dynamic' | 'kinematic'packages/physics/src/kernel.ts:18
CombineRule type
type CombineRule = 'mean' | 'geometric' | 'min' | 'max' | 'multiply'packages/physics/src/materials.ts:19
ConstraintType type
type ConstraintType = 'rod' | 'rope' | 'spring' | 'ball' | 'hinge' | 'fixed'A link between two bodies' centres.
- rod: a rigid distance (pushes and pulls)
- rope: a maximum distance (pulls only; slack when closer)
- spring: Hooke's law with damping (implicit, so stable at any stiffness)
- ball: the two bodies share a point and turn freely about it (a shoulder, a hook)
- hinge: they share a point and turn about one axis only, within optional limits (a door, a wheel)
- fixed: they are welded in the pose they have (a bracket that can break)
The last three need a kernel with caps.joints (rigid bodies).
packages/physics/src/kernel.ts:139
Phase type
type Phase = 'field' | 'strength' | 'stretch'The fixed order of the step: fields add forces, the kernel integrates and resolves contacts, strength reads them, stretch integrates soft things.
packages/physics/src/host.ts:10
Quat type
type Quat = [number, number, number, number]packages/physics/src/vec.ts:4
Scope type
type Scope = 'world' | 'level' | 'zone' | 'entity'packages/physics/src/host.ts:39
Shape type
type Shape = | { type: 'sphere'; radius: number } | { type: 'box'; half: Vec3 } // static or kinematic only in the light kernel | { type: 'plane'; normal: Vec3; offset: number } // static only: dot(normal, p) = offset // the shapes below need a rigid-body kernel (Havok); the light kernel refuses them | { type: 'capsule'; radius: number; halfHeight: number } // along the body's y, halfHeight to the centre of each cap | { type: 'cylinder'; radius: number; halfHeight: number } // along the body's y | { type: 'convex'; points: Vec3[] } // the hull of the points, in body space | { type: 'mesh'; positions: number[]; indices: number[] } // triangles in body space; static or kinematic only | { type: 'compound'; children: { shape: Shape; position?: Vec3; orientation?: Quat }[] }The part of the dynamics every capability is written against. Havok, the light kernel of this package, or a game's own backend
implements it; a capability asks caps what is native and does the rest in a force or contact hook.
packages/physics/src/kernel.ts:7
ShapeKind type
type ShapeKind = 'box' | 'sphere' | 'capsule'packages/physics/src/physics.ts:4
Tier type
type Tier = 'desktop' | 'mobile' | 'console' | 'low'What a hardware tier can afford, and what to give up when the world step runs over its share of the frame.
The numbers in TIERS come from tools/bench (2026-11-02), run on the dev machine in Node, Chromium and WebKit: a full step share of 6 ms holds
about 200 resting pieces, or about 170 to 290 liquid particles, or about 230 cloth particles when each takes 40 / 30 / 30 % of it. The console,
mobile and low tiers are scaled down from the same table; they are not measured on those machines: run tools/bench there and replace them.
flowBoxCells comes from the flow box's measured 0.9 microseconds per cell per step (A13) at 30 % of the step share: 1.8 ms / 0.9 us = 2000 cells
stepped every physics step on the desktop (a box stepped every n-th step may have n times as many).
packages/physics/src/budget.ts:11
Vec3 type
type Vec3 = [number, number, number]Plain-number vectors for the physics code: no allocation-heavy classes, no engine dependency, and the same arithmetic everywhere so a replay gives the same bits. A vector is [x, y, z]; a quaternion is [x, y, z, w].
packages/physics/src/vec.ts:3
Constants
DEFAULT_COMBINE const
const DEFAULT_COMBINE: CombineRulespackages/physics/src/materials.ts:22
DEFAULT_CONTACT const
const DEFAULT_CONTACT: { friction: number; restitution: number; rollingFriction: number; pivotFriction: number; }packages/physics/src/materials.ts:23
DEFAULT_LAYERS const
const DEFAULT_LAYERS: string[]The layers a game gets unless it defines its own (the common UE object channels).
packages/physics/src/layers.ts:35
EXAMPLE_MATERIALS const
const EXAMPLE_MATERIALS: Record<string, PhysicsMaterial>A few common materials, as examples and for tests. A game defines its own.
packages/physics/src/materials.ts:106
TIERS const
const TIERS: Record<Tier, TierBudget>packages/physics/src/budget.ts:24