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
strengthcapability 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.
| Value | Default | Effect |
|---|---|---|
threshold | the kernel's 0.2 m/s | closing speed below which a bounce is a rest |
scale | 1 | multiplies the restitution. 0 = every hit is inelastic |
falloff | none | { from, to, factor }: restitution falls linearly from 1× at from m/s to factor× at to m/s |
stick | false | two dynamic bodies that hit leave as one body |
minEventSpeed | 0.1 | smallest 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#
| Value | Scopes | Unit | Safe range |
|---|---|---|---|
threshold | all | m/s | 0.05 – 2 |
scale | all | – | 0 – 1.5 |
falloff.from, falloff.to | all | m/s | from < to |
falloff.factor | all | – | 0 – 1 |
stick | all | boolean | |
minEventSpeed | all | m/s | 0 – 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#
frictionruns first in the same phase; both change the contact before the solve.strengthreads 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#
| Symptom | Cause |
|---|---|
| Everything bounces forever | restitution is high and threshold low: raise threshold, or scale below 1 |
No physics:impact events | the capability is off, or minEventSpeed is above the closing speeds |
| Bodies do not stick | stick 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 appear | scale above 1 gives restitution above 1: keep it at 1 or below |
A bounce height is not e² h | friction, spin, a slope or the threshold took part; test with spin-free spheres |
Reference#
impact, ImpactEvent, ImpactFalloff, IMPACT_DEFAULTS, ContactEvent, @ferret/physics-impact.