Explosions
Package @ferret/physics-explosions, capability explosion.
What it is for#
A grenade, a gas cylinder that bursts, a mine, a charge under a bridge, a shell. An explosion here is energy delivered through scaling laws. The other capabilities do the rest: bodies fly through the kernel, walls break through strength, the ground opens through terrain, and fire is the job of combustion and heat.
How a blast behaves#
- Scaling. The blast depends on the distance divided by the cube root of the charge. The same effect at eight times the charge is twice as far away. The overpressure at a given distance comes from a standard curve, together with how long the pressure lasts; the push a body gets is the pressure times its duration, over a projected area.
- The push. A body gets its impulse as a shove along the line from the charge, when the front arrives (distance over the speed of the shock wave). Bodies behind other bodies get less.
- Kinds of burst. In open
airthe blast spreads freely. On thesurfacethe ground reflects it, which counts as 1.8 times the charge. Aburiedcharge loses much of its blast to the soil, and a deep enough one opens no crater at all (a camouflet). Awaterburst uses the underwater curve, and the front travels at 1480 m/s. - How big. Give the charge as
tnt(kilograms), asenergy(joules), or as avesselthat bursts (pressure,volume,gamma), which takes the energy from the gas's expansion. - Casings. A casing around the charge breaks into fragments of varied size which leave at a speed set by the ratio of casing to charge.
- Walls and wind. A wall reflects a weak wave to twice its pressure and a strong one up to eight times. A wind behind the front pushes as well. A wall can also spall: a slab flies off its back side when the pulse is strong and long enough.
How far does it reach#
These functions need no world, which makes them useful for danger rings in a game UI and for design.
import { blastAt, radiusFor, vesselTnt } from '@ferret/physics-explosions';
const grenade = 0.2; // kg of TNT
const at5m = blastAt(5, grenade); // { overpressure, duration, impulse, arrival, scaled }
const lethal = radiusFor(70e3, grenade); // metres where the overpressure has fallen to 70 kPa
const glass = radiusFor(2e3, grenade); // where windows break (2 kPa)
const cylinder = vesselTnt(150e5, 0.05); // a 50 litre cylinder at 150 bar, as kg of TNT
In a world, cap.preview(tnt, pressures) and the command explosion.preview give the same rings.
Blowing things up in a world#
import { explosions } from '@ferret/physics-explosions';
const x = explosions();
host.register(x);
host.enable('explosion');
const result = x.detonate({ at: [0, 0, 0], tnt: 10 }); // { tnt, effective, bodies, fragments, crater?, camouflet }
A body within range receives the impulse of the blast times its exposed area. Command: explosion.detonate { at, tnt | energy | vessel, burst?, burial?, casing?, seed? }. The same seed gives the same fragments.
Making things break#
Set rupture, the impulse per area that a body can take (in Pa·s; Infinity means never). With strength on, any body that receives at least that much breaks (explosion:break) and its pieces carry the momentum of the blast.
host.config('explosion').set('world', 'rupture', 200); // every body
host.config('explosion').set('entity', 'rupture', 20, 'glass-pane'); // one weak entity
For a wall, give wall: { tensile, density, waveSpeed, thickness }. A pulse that tears a slab off its back raises explosion:spall, even when the wall itself stays up.
Hiding behind something#
A body between the charge and a target blocks the blast. The target gets the share transmission (0.1 by default), which you can set per scope: 0.5 for a thin screen, 0.01 for a bunker wall.
explosion:exposure { entity, overpressure, impulse, shielded, reflected, wind } tells every exposed entity what it received, including entities that only carry the exposure tag, such as a player's character or a window, and have no body. That is the event to use for damage.
A grenade with a casing#
x.detonate({ at: [0, 1, 0], tnt: 0.2, casing: { mass: 0.4, shape: 'cylinder' }, seed: 7 });
Fragments are created over several steps (perFrame, 60), at most maxFragments (300) at once, each living fragmentLife seconds (8); the oldest are recycled. The event explosion:fragments { at, count, speed, mass } announces them, and a fragment that touches something raises explosion:hit { fragment, target, speed, energy, point, outcome? }.
Armour#
A fragment hits a plate: with the armour value { thickness, strength } on the plate, penetrate compares the fragment's energy with the work of pushing a hole through. The outcome is through (with the speed it leaves at), stopped, or ricochet when it strikes at a grazing angle. A plate without armour simply makes fragments bounce.
Craters#
Give the capability a terrain (x.setTerrain(terrain) or explosions({ terrain })) and a surface or buried charge digs a crater, sized by the charge, the burial and the soil (sand, clay, silt, gravel, mud, rock). The event explosion:crater reports radius, depth, the volume removed and thrown, and how far the ejecta fly. A deep enough burial gives camouflet: true and no crater. Terrain says what the ground does next: slumping, flooding.
A vessel that bursts#
x.detonate({ at, vessel: { pressure: 150e5, volume: 0.05, gamma: 1.4 }, burst: 'air' });
The energy is the gas's expansion work. A boiler or a tank that goes over its limit in combustion calls this itself, with the mass of its casing for the fragments.
Parameters#
| Value | Default | Effect |
|---|---|---|
minPressure | 500 Pa | smallest overpressure a body bothers with |
rupture | Infinity | impulse per area that breaks a body (needs strength) |
area | projected area | exposed area of a body, m² |
transmission | 0.1 | share of the blast a shielding body lets through |
spread | 0 | random tilt of the push, in radians |
armour | none | a plate { thickness, strength } that fragments meet |
wall | none | { tensile, density, waveSpeed, thickness } for spalling |
Options of explosions(): terrain, curve, craters, maxFragments, perFrame, fragmentLife.
Cost#
A table lookup and a few rays per body per blast. Fragments are capped and made over several steps. A blast with hundreds of bodies in range is a one-frame spike, not a continuing cost. Spread large scenes over a few frames, or reduce the number of targets with minPressure.
When it does not behave#
| Symptom | Cause |
|---|---|
| Nothing moves | the body is below minPressure at its distance; check blastAt(r, tnt).overpressure |
| A body moves less than expected | it is shielded; read shielded in explosion:exposure |
| Nothing breaks | rupture is Infinity (the default), or strength is not enabled |
| Wrong crater | no terrain, the burst is air, or the soil is missing from craters (sand is used) |
| Fragments do nothing to a plate | the plate has no armour value |
| Different fragments each time | no seed; pass one for a replay |
Worked numbers (scaling, impulse on a crate, Gurney velocity, penetration outcomes) are in tests/unit/examples/physics-explosions.test.ts.
Reference#
explosions, ExplosionCapability, blastAt, radiusFor, vesselTnt, gurneyVelocity, mottMasses, craterRadius, penetrate.