FERRETGame engine over Babylon.js
Ferret

Friction

Package @ferret/physics-friction, capability name friction, phase strength (order −20: it acts before impact and before breakage).

What it is for#

The kernel already applies Coulomb friction from the materials of the two surfaces. This capability adds what a game dials around that: a patch of ice in one room, a puck that has no grip, a ball that rolls farther on a polished floor, a skate that slides along its blade and bites sideways. Materials describe stuff; friction describes places and moods.

The model#

At each contact the friction coefficients come from the material pair (materials); then this capability multiplies them by the scoped values at the contact (the dynamic body's entity, and the contact point, so a zone applies):

ValueDefaultEffect
scale1multiplies static and kinetic friction. 0 = frictionless, 0.05 = ice, 2 = tar
rollingScale1multiplies rolling resistance
pivotthe material'spivot (spinning about the contact normal) friction coefficient
anisotropynone{ axis: [x,y,z], across }: across the axis the coefficient is across times the one along it

Kinetic friction defaults to 0.8 × the static coefficient when the material gives only the static one. A sliding body slows at μk · g (on flat ground).

First use#

import { PhysicsHost, RigidBody } from '@ferret/physics';
import { friction } from '@ferret/physics-friction';

const host = world.addService(new PhysicsHost({ capabilities: [friction()] }));
host.materials.add('floor', { friction: 0.6, restitution: 0 }).add('puck', { friction: 0.6, restitution: 0 });
host.kernel.addBody({ shape: { type: 'plane', normal: [0, 1, 0], offset: 0 }, material: 'floor' });
host.enable('friction');

const puck = (name: string, x: number) => {
  const e = world.spawn(name, { position: new BABYLON.Vector3(x, 0.2, 0) });
  e.add('RigidBody', { shape: 'sphere', radius: 0.2, mass: 1, material: 'puck', velocity: [4, 0, 0], lockRotation: true });
  return e;
};
const normal = puck('normal', 0), greased = puck('greased', 20);
host.config('friction').set('entity', 'scale', 0.05, greased.id);
world.run(0.5);

lockRotation: true makes the sphere slide like a block (a puck, a crate) instead of rolling.

Recipes#

An icy room#

A zone with a low scale makes everything in it slippery and nothing outside:

host.config('friction').set('zone', 'scale', 0.05, 'ice-rink');

The same from a level: "friction": { "scale": 0.05 } is level-wide; per-zone values go through the same call (or an agent's physics.configure).

A skate, a ski, a rail#

Anisotropy gives different grip along and across a direction. axis is the along direction in the world; across it the coefficient is across times larger. A skate on ice: a tiny friction on the blade material and across ≈ 20–40 so it slides forward and holds sideways:

import { PhysicsHost, RigidBody } from '@ferret/physics';
import { friction } from '@ferret/physics-friction';

const host = world.addService(new PhysicsHost({ capabilities: [friction()] }));
host.materials.add('ice', { friction: 0.6, restitution: 0 }).add('blade', { friction: 0.02, restitution: 0 });
host.kernel.addBody({ shape: { type: 'plane', normal: [0, 1, 0], offset: 0 }, material: 'ice' });
host.enable('friction');
host.config('friction').set('world', 'anisotropy', { axis: [1, 0, 0], across: 40 });
const skate = world.spawn('skate', { position: new BABYLON.Vector3(0, 0.2, 0) });
skate.add('RigidBody', { shape: 'sphere', radius: 0.2, mass: 1, material: 'blade', velocity: [5, 0, 5], lockRotation: true });
world.run(0.5);

Anisotropy works on the light kernel (it needs contact hooks).

Rolling resistance#

A rolling ball slows at about (5/7) · μr · g (a solid sphere), where μr is the material's rollingFriction. rollingScale multiplies it: 0.5 means the ball rolls twice as far. Havok has no rolling resistance of its own; the capability adds it as a torque against the roll, limited so it can never reverse the spin within a step.

Spinning in place#

pivot is resistance to turning about the contact normal: a coin spinning on a table, a puck that should stop turning. Set it per scope or in the material (pivotFriction). Light kernel.

A sticky or slick material instead#

Prefer a material when it is a property of the stuff (rubber is grippy everywhere). Use scale when it is a property of this place or this moment (a spill, a rink).

Parameters#

ValueScopesUnitSafe range
scaleworld, level, zone, entity–0 – 5
rollingScaleall–0 – 5
pivotall–0 – 1
anisotropyall{axis, across}across 1 – 100

Kernel differences#

Light kernelHavok
scaleat every contact, before the solveapplied to each body's surface (its material's friction times the scale where it is) when the scale changes
rollingScalenativerolling is a torque added by the capability
pivot, anisotropyyesno (need contact hooks)

Interplay#

  • Order: friction runs before impact in the same phase, so both change a contact before it is solved.
  • Soil that deforms and sinks is the job of terrain; use friction for the grip of rigid surfaces.

Budget#

One hook per contact on the light kernel. On Havok: one surface update per body when its scale changes, plus a torque per rolling body. Negligible next to the solve.

Debugging#

SymptomCause
Nothing slides on icethe capability is not enabled, or the scope key is wrong: the value is read at the dynamic body's entity and the contact point
A zone does nothingno ZoneService, or the body's position is outside the zone
A ball never stops rollingthe material has no rollingFriction, and rollingScale multiplies zero
Anisotropy or pivot has no effectyou are on Havok
A block creeps on a slopestatic friction below the tangent of the slope: compare μ with tan(angle)

Reference#

friction, FRICTION_DEFAULTS, scopedSurface, @ferret/physics-friction.