FERRETGame engine over Babylon.js
Ferret

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 air the blast spreads freely. On the surface the ground reflects it, which counts as 1.8 times the charge. A buried charge loses much of its blast to the soil, and a deep enough one opens no crater at all (a camouflet). A water burst uses the underwater curve, and the front travels at 1480 m/s.
  • How big. Give the charge as tnt (kilograms), as energy (joules), or as a vessel that 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#

ValueDefaultEffect
minPressure500 Pasmallest overpressure a body bothers with
ruptureInfinityimpulse per area that breaks a body (needs strength)
areaprojected areaexposed area of a body, m²
transmission0.1share of the blast a shielding body lets through
spread0random tilt of the push, in radians
armournonea plate { thickness, strength } that fragments meet
wallnone{ 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#

SymptomCause
Nothing movesthe body is below minPressure at its distance; check blastAt(r, tnt).overpressure
A body moves less than expectedit is shielded; read shielded in explosion:exposure
Nothing breaksrupture is Infinity (the default), or strength is not enabled
Wrong craterno terrain, the burst is air, or the soil is missing from craters (sand is used)
Fragments do nothing to a platethe plate has no armour value
Different fragments each timeno 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.