Media
Package @ferret/physics-media, capability name media, phase field (order 10).
What it is for#
Anything that moves through something. A medium is a substance with a density and a viscosity (and maybe a flow) that fills a place; a body in it feels
- drag against the flow: Stokes' law at low Reynolds numbers, a quadratic law at high ones (Schiller–Naumann for a sphere), so a feather, a stone and a cannonball each fall as their size says;
- buoyancy: ρ_medium · V_submerged · g from the exact submerged part of the body, so wood floats at a draft that depends on its density, lead sinks, a hull rights itself;
- rotational drag.
Air is the default medium (density and temperature from an atmosphere by altitude), can have a wind with gusts, and can be switched off for vacuum. A body half in water and half in air feels the volume-weighted blend. It is not a fluid simulation: the medium is a property of a place. For flowing, splashing water use liquids.
The model#
medium at p = the dense medium whose region contains p (surface, zone, bounds) else air
body feels = blend of the media by the submerged fraction, with their flows
force = buoyancy (along gravity) + drag against (body velocity − flow)
A medium (MediumDef) is { name, density (kg/m³), viscosity (Pa·s), flow?, surface?, zone?, bounds?, gust? }:
| Field | Meaning |
|---|---|
surface | the y of the top of the medium, or a function when it moves (a reservoir that fills and drains). Below it the medium is there |
zone | limits the medium to a named zone of @ferret/zones |
bounds | {min, max} box the medium fills (a tank); outside it the medium is not there |
flow | velocity of the medium, m/s: a current, a river |
none of surface, zone, bounds | fills everything |
EXAMPLE_MEDIA has water, seawater, mud and honey to start from.
First use#
import { PhysicsHost, RigidBody } from '@ferret/physics';
import { gravity } from '@ferret/physics-gravity';
import { media, EXAMPLE_MEDIA, submergedFraction } from '@ferret/physics-media';
const m = media();
const host = world.addService(new PhysicsHost({ capabilities: [gravity(), m] }));
host.enable('gravity'); host.enable('media');
m.addMedium({ ...EXAMPLE_MEDIA.water, surface: 10 }); // a lake with its top at y = 10
const ball = (name: string, x: number, density: number) => {
const r = 0.5;
const e = world.spawn(name, { position: new BABYLON.Vector3(x, 11, 0) });
e.add('RigidBody', { shape: 'sphere', radius: r, mass: density * (4 / 3) * Math.PI * r ** 3, linearDamping: 4 });
return e.get(RigidBody)!.body;
};
const wood = ball('wood', 0, 600), lead = ball('lead', 3, 3000);
world.run(12);
const draft = submergedFraction(wood.position[1], 0.5, 10);
Wood of density 600 settles with 60 % of its volume under: ρ_body / ρ_water, as Archimedes says. Lead sinks and falls at the terminal speed its drag allows.
Recipes#
Wind that gusts#
wind is a vector at a reference height (windRefHeight, 10 m); windExponent gives a power-law profile with height (0 = uniform); gust: { amplitude, period, seed? } adds a deterministic gust along it: the same seed gives the same gusts in every run and on every client.
import { PhysicsHost, RigidBody } from '@ferret/physics';
import { media, uniformAtmosphere } from '@ferret/physics-media';
const host = world.addService(new PhysicsHost({ capabilities: [media()] }));
host.kernel.gravity = [0, 0, 0];
host.enable('media');
host.config('media').set('world', 'atmosphere', uniformAtmosphere(1.2));
host.config('media').set('world', 'wind', [10, 0, 0]);
const leaf = world.spawn('leaf', { position: new BABYLON.Vector3(0, 5, 0) });
leaf.add('RigidBody', { shape: 'sphere', radius: 0.1, mass: 50 * (4 / 3) * Math.PI * 0.001 });
world.run(60);
A body in a flow is carried toward the flow's speed and never past it: drag acts on the relative velocity.
A river or a current#
m.addMedium({ ...EXAMPLE_MEDIA.water, surface: 100, flow: [0, 0, 2] }): anything in the water drifts toward 2 m/s along z.
Mud, honey and tar pits#
Same call with a different medium: EXAMPLE_MEDIA.mud (1800 kg/m³, 0.5 Pa·s) or honey (10 Pa·s). The high viscosity gives Stokes drag: a small sphere sinks at 2 r² (ρs − ρ) g / (9 μ), slowly. Limit a bog to a place with zone: 'bog'. For soil that deforms, sinks under load and floods, use terrain; mud as a medium is drag and buoyancy only.
Walking, wading and swimming#
The character controller samples the medium at the character: air changes nothing, water slows a runner to what its push can do. A level that puts water with a surface over a pool therefore makes wading work with no code.
Vacuum and other planets#
host.config('media').set('world', 'air', false) makes a vacuum: no drag at all, a rock keeps its speed. Another planet's air is an Atmosphere: standardAtmosphere (the International Standard Atmosphere up to 47 km), exponentialAtmosphere(seaLevelDensity, scaleHeight, temperature) for a game world, uniformAtmosphere(density) for a closed hall, vacuum. seaLevel is the y of altitude zero.
A closed volume of gas#
GasVolume is a bounded amount of gas obeying p·V = n·R·T: a balloon envelope, an air pocket in a ballast tank, a diving bell. It is one pressure and one temperature for the whole volume, not a flow.
import { GasVolume, standardAtmosphere } from '@ferret/physics-media';
const g = GasVolume.ofAir(standardAtmosphere, 0, 2); // 2 m³ of sea-level air
const p0 = g.pressure, T0 = g.temperature;
g.heat(g.cv * 30); // +30 K at constant volume
const hot = g.pressure;
const squeezed = GasVolume.ofAir(standardAtmosphere, 0, 2);
squeezed.setVolume(1, 'adiabatic'); // halve the volume without letting the heat out
heat(joules, 'volume' | 'pressure'), setVolume(v, 'isothermal' | 'adiabatic'), addMoles(dn) (a leak lowers the pressure). Vessels use gas volumes for ballast and buoyancy; see vehicles.
Parameters#
| Value | Default | Effect |
|---|---|---|
air | true | air is a medium; false = vacuum |
atmosphere | standardAtmosphere | density, pressure, temperature by altitude |
seaLevel | 0 | y of altitude zero |
wind | [0,0,0] | m/s at windRefHeight |
windRefHeight, windExponent | 10, 0 | power-law profile; 0 = the same wind at every height |
gust | none | { amplitude, period, seed? } along the wind |
media | [] | list of MediumDef; addMedium / removeMedium edit it |
cd | by Reynolds number | a constant drag coefficient instead of the law; an entity may set its own |
angularDrag | true | rotational drag |
samples | 4 | how many points a body is sampled at when it is part in, part out of a medium |
All scoped (world, level, zone, entity), so one room can be windy and one boat can be draggy.
Commands#
media.add {name, density, viscosity, flow?, surface?, zone?}, media.remove {name}, media.at {position} (the medium there), atmosphere.at {altitude} (density, pressure, temperature), wind.set {vector, gust?, scope?}.
Interplay#
gravitygives the direction and strength buoyancy acts against; with it off, the kernel's constant is used.terrainreportsterrain:floodedwhen a dig reaches the water table. It does not add a medium by itself: put awatermedium over the flooded area (asurfaceat the water table's height) so things float in it.- Vessels (
vehicles) compute their own weight and buoyancy from hull volume and are markedexemptfromgravity,mediaandsurface; read vehicles for how the water they sit in acts on them. - Characters read
fields.mediumandfields.column(how much of a vertical segment stands in a dense medium) to wade and swim.
Budget#
A medium lookup plus a few samples per body per step. Cost grows with the number of bodies in a dense medium, not with the number of media. A hundred bodies in a lake is cheap; if the step runs long, lower samples.
Debugging#
| Symptom | Cause |
|---|---|
| A body sinks that should float | its density is above the medium's (mass / volume): check the mass against the shape and the units |
| It floats but bobs forever | no damping: water drag damps it only as long as it moves; give linearDamping for a crude fix, or let the quadratic drag work (do not set cd to 0) |
| Nothing happens in the water | media is not enabled, or the body is not inside the medium's region: media.at at the body's position tells which medium it is in |
| A pool medium is everywhere | with neither surface, zone nor bounds a medium fills everything: give it a place |
| Wind does nothing in a closed room | bounds or a zone limit the dense media; the wind is air's: set wind in the room's zone scope |
| A body in a vacuum keeps its speed | that is correct: air: false removes drag entirely |
Reference#
media, MediaCapability, MediumDef, MEDIA_DEFAULTS, EXAMPLE_MEDIA, submergedFraction, sphereCd, gustAt, standardAtmosphere, exponentialAtmosphere, GasVolume, @ferret/physics-media.