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):
| Value | Default | Effect |
|---|---|---|
scale | 1 | multiplies static and kinetic friction. 0 = frictionless, 0.05 = ice, 2 = tar |
rollingScale | 1 | multiplies rolling resistance |
pivot | the material's | pivot (spinning about the contact normal) friction coefficient |
anisotropy | none | { 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#
| Value | Scopes | Unit | Safe range |
|---|---|---|---|
scale | world, level, zone, entity | – | 0 – 5 |
rollingScale | all | – | 0 – 5 |
pivot | all | – | 0 – 1 |
anisotropy | all | {axis, across} | across 1 – 100 |
Kernel differences#
| Light kernel | Havok | |
|---|---|---|
scale | at every contact, before the solve | applied to each body's surface (its material's friction times the scale where it is) when the scale changes |
rollingScale | native | rolling is a torque added by the capability |
pivot, anisotropy | yes | no (need contact hooks) |
Interplay#
- Order:
frictionruns beforeimpactin the same phase, so both change a contact before it is solved. - Soil that deforms and sinks is the job of terrain; use
frictionfor 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#
| Symptom | Cause |
|---|---|
| Nothing slides on ice | the 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 nothing | no ZoneService, or the body's position is outside the zone |
| A ball never stops rolling | the material has no rollingFriction, and rollingScale multiplies zero |
| Anisotropy or pivot has no effect | you are on Havok |
| A block creeps on a slope | static friction below the tangent of the slope: compare μ with tan(angle) |
Reference#
friction, FRICTION_DEFAULTS, scopedSurface, @ferret/physics-friction.