FERRETGame engine over Babylon.js
Ferret

Impact

Package @ferret/physics-impact, capability name impact, phase strength (order −10: after friction, before breakage).

What it is for#

The kernel bounces bodies by the restitution of their two materials. impact adds what a game wants to control per place and per hit:

  • a bounce that fades at high speed (a ball that splats instead of ricocheting);
  • a rest threshold: the speed below which a bounce is a rest, so things settle instead of buzzing;
  • sticking: two bodies that hit leave as one (clay, a grappling hook, a magnet);
  • an event per hit with the impulse, the speed and the energy lost, so you can play a thud, spawn dust, deal damage. The strength capability reads the same hits.

The model#

For each contact the capability changes how it is solved (restitution, rest, stick), and after the step it reports what happened. The loss of energy follows physics: with the reduced mass μ = m₁m₂/(m₁+m₂) and closing speed v,

energyLost = ½ · μ · (1 − e²) · v²

which equals the kinetic energy the pair really lost.

ValueDefaultEffect
thresholdthe kernel's 0.2 m/sclosing speed below which a bounce is a rest
scale1multiplies the restitution. 0 = every hit is inelastic
falloffnone{ from, to, factor }: restitution falls linearly from 1× at from m/s to factor× at to m/s
stickfalsetwo dynamic bodies that hit leave as one body
minEventSpeed0.1smallest closing speed that raises physics:impact

All scoped (world, level, zone, entity).

First use#

import { PhysicsHost, RigidBody } from '@ferret/physics';
import { impact, type ImpactEvent } from '@ferret/physics-impact';

const host = world.addService(new PhysicsHost({ capabilities: [impact()] }));
host.kernel.gravity = [0, 0, 0];
host.materials.add('m', { restitution: 1, friction: 0 });
host.enable('impact');
host.config('impact').set('world', 'scale', 0.5);                      // every hit has e = 0.5

const ball = (name: string, x: number, mass: number, vx: number) => {
  const e = world.spawn(name, { position: new BABYLON.Vector3(x, 0, 0) });
  e.add('RigidBody', { shape: 'sphere', radius: 0.5, mass, material: 'm', velocity: [vx, 0, 0] });
  return e;
};
const hits: ImpactEvent[] = [];
world.events.on('physics:impact', (e: ImpactEvent) => hits.push(e));
const a = ball('a', -3, 2, 4), b = ball('b', 3, 3, -1);
world.run(3);

Recipes#

Make things settle#

A ball dropped on a floor with restitution 0.7 bounces many times, each smaller. Raise threshold (say 1 m/s) so the small final bounces become rests: the ball stops instead of vibrating. Do it in a zone to settle only the places that matter.

A bounce that dies at high speed#

falloff: { from: 5, to: 20, factor: 0.2 }: below 5 m/s the material's bounce stands; at 20 m/s and above it is a fifth of that. A dropped rubber ball bounces; the same ball shot from a cannon splats.

Clay, glue and magnets#

stick: true (world, zone or entity): two dynamic bodies that hit leave moving at (m₁v₁ + m₂v₂)/(m₁+m₂) and stay in contact. Conserves momentum exactly, loses the energy ½ μ v². ImpactEvent.stuck is true for those hits. Scope it to an entity to make one thing sticky (a flypaper, a grappling hook).

Sound and sparks from hits#

Listen to physics:impact (an ImpactEvent: a, b, entityA, entityB, point, normal, impulse, speed, restitution, energyLost, stuck) and map speed or impulse to volume. Pick the sound by the two materials' names (a.material, b.material). Do not play a sound for every event: use minEventSpeed (e.g. 1 m/s) to ignore resting contact.

Damage from impacts#

Do not invent damage here: switch on strength, which reads impact energy against the material's strengths. Use the energyLost of the event only for game logic that is not about breaking (a score for a big hit).

Parameters#

ValueScopesUnitSafe range
thresholdallm/s0.05 – 2
scaleall–0 – 1.5
falloff.from, falloff.toallm/sfrom < to
falloff.factorall–0 – 1
stickallboolean
minEventSpeedallm/s0 – 5

Kernel differences#

threshold, scale and falloff need contact hooks on the light kernel. On Havok the restitution is each body's material value times scale (the surface update mechanism of friction); falloff and stick are applied through contact hooks, which Havok does not offer: test them on the light kernel before relying on them.

Interplay#

  • friction runs first in the same phase; both change the contact before the solve.
  • strength reads the same contacts to damage and break.
  • The events leave on the world's bus once per step, after the whole step.

Budget#

One hook per contact and one event per contact with speed above minEventSpeed. In a pile of 200 resting pieces the event filter is what keeps it cheap: raise minEventSpeed.

Debugging#

SymptomCause
Everything bounces foreverrestitution is high and threshold low: raise threshold, or scale below 1
No physics:impact eventsthe capability is off, or minEventSpeed is above the closing speeds
Bodies do not stickstick must be set where the contact is (the dynamic body's scope) and both bodies must be dynamic; on Havok it is not available
Energy seems to appearscale above 1 gives restitution above 1: keep it at 1 or below
A bounce height is not e² hfriction, spin, a slope or the threshold took part; test with spin-free spheres

Reference#

impact, ImpactEvent, ImpactFalloff, IMPACT_DEFAULTS, ContactEvent, @ferret/physics-impact.