Flow sources
Package @ferret/flow (no physics, no Babylon), service flow.
The field#
FlowField.at(position, time, medium) returns { velocity, updraft?, turbulence? } for air or water: the sum of the level's sources, then the shelters reduce it. Every source is a pure function of position and time, so every peer computes the same field with no replication, and the render can sample it without physics.
"physics": {
"capabilities": ["media"],
"flow": [
{ "name": "breeze", "kind": "uniform", "velocity": [6, 0, 2], "profile": { "power": 0.14, "refHeight": 10 }, "gust": { "amplitude": 0.3, "period": 7 } },
{ "name": "twister", "kind": "vortex", "centre": [0, 0, 0], "core": 40, "circulation": 9000, "inflow": 0.3, "updraft": 25,
"height": 600, "reach": 400, "path": [[0, 0, 0], [800, 0, 200]], "speed": 12, "level": { "curve": [[0, 0], [20, 1], [120, 1], [160, 0]] } },
{ "name": "mill-race", "kind": "channel", "medium": "water", "points": [[0, 0, 0], [0, 0, 40]], "velocity": 2.5, "width": 1.6 }
]
}
| Kind | Parameters |
|---|---|
uniform | velocity, optional power-law profile by height, optional gust (the deterministic gust of physics-media) |
front | origin, horizontal direction, speed, width, before and after velocities: a plane that travels; behind it the wind changes across the width |
vortex | centre, core, circulation Γ: solid rotation inside the core, Γ/2πr outside, counter-clockwise from above; inflow (ratio of radial inflow to swirl), updraft in the core, height |
jet | origin, direction, velocity, halfAngle, decay length: a fan, a vent, a propeller wash |
shelter | centre, radius, strength 0..1: reduces the flow of the other sources in the lee and reports turbulence |
channel | points, velocity, width: a flow along a polyline with a parabolic cross profile (a river, a race) |
curve | origin, cell, size, data: a baked grid of velocities (3 numbers per cell), trilinear inside, nothing outside |
Every source also takes medium (air default, or water), on, level (a number or a curve [[t, v], ...], a storm at noon), reach (a sphere outside which it adds nothing and is not evaluated) and path + speed + start (it moves along points).
Who reads it#
physics-media adds the field's air flow to the wind of its settings, and the field's water flow to every liquid medium (density from 100 kg/m³), so drag, lift and buoyancy of bodies follow it, in the air and in a mill race or a whirlpool; the old wind and gust settings keep working unchanged. Vessels (Vessel.windAt: sails, control surfaces, hull drag in air), the character (CharacterMovement is pushed by the wind by its area and mass, at most maxWindAccel m/s², 0 turns it off) and a liquid surface (waterCurrent(field) as its current) read it too. The render comes with stage A9. FlowField.maxSources is the budget of a console tier.
Media zones in the field#
The own flow and gust of a medium (a canyon, a current in a tank) are published into the field as a uniform source named media:<medium>, limited to the medium's zone, bounds and surface (air for a light medium, water for a liquid). So vessels, the character and the render see them. physics-media skips its own publications when it reads the field, so nothing is counted twice, and list / edit.physics.flow.list show level data only.
Commands#
flow.at { position, medium?, time? }, flow.set { name, ... }, flow.on, flow.off, flow.move { name, path, speed }; editor: edit.physics.flow.set, .remove, .list. FlowService.load(list) reads a level's flow list.
Over the net (A8)#
The field is a pure function of position and time, so only changes travel. netFlow(field, net, { authority, authorityPeer, now, delay }): the authority's add/set/remove carry an effect time and every machine applies them at that time (call step() every fixed step). A machine that is not the authority cannot change the weather, and messages from any other peer are ignored.
For the inspector and the picture (A9)#
Commands: flow.grid {centre, half, step} (arrows), flow.reach (each source's centre now, reach and vortex core), flow.funnels. Render numbers, pure functions of the field and the time (the same sky on every peer): patchWind (a wind uniform per patch of grass or trees), advectClouds, plumeLean (smoke and steam), dropVelocity (rain and snow slant), funnels, and turbulenceNoise(seed, p, t), a smooth seeded noise the picture may add to ragged a gale; the physics never reads it. flowArrows in physics-fx draws the grid as lines.
Flow boxes: flow around bodies (A13)#
@ferret/flow-solver. The field above is a sum of analytic sources; bodies read it and do not change it. A flow box is a bounded slice where the flow is solved, so a body in it changes the flow and the flow pushes the body.
- Solver (
FlowGrid): incompressible flow in two dimensions on a staggered grid. Advection by MacCormack with a limiter (a coarse grid keeps its eddies), explicit viscosity, optional vorticity confinement, and a pressure projection by conjugate gradient with a modified incomplete Cholesky preconditioner, warm-started from the last step, at most 40 iterations (a fixed cost per tier). It depends only on its data, never on the clock, so two peers with the same history have the same field (tested bit for bit). - Box (
FlowBox):plane: 'xz'is a horizontal slice at heightorigin[1](the streets of a town, a river from above),'xy'a vertical one (a roof, a wing). Each step it samples the analytic field of the level (everything but itself) on its edges: a side the wind blows into is an inflow, a side it leaves by is open, a side with no normal wind is a wall. It publishes itself into@ferret/flowas asolvedsource: inside the box the solver's velocity replaces the analytic sum (blended overmargincells), the component off the slice stays the analytic one. Media, vessels, the character, rotors, sails and the picture read the wake without any change.medium: 'water'does the same for the water readers andwaterCurrent. - Bodies:
circle,box,polygonin the plane,static(voxelised once) or moving (setBody, each step: the cells they cover take the body's velocity, so a swung door pushes the air).force(id)is the pressure and wall shear on the surface times the body'sdepth. - In a level (
flowBoxes(fieldOf)capability, blockphysics.flowBoxes): bodies with anodefollow that rigid body.forces: 'solved'applies the force to it and switches the media drag of a body in an air box off (exempt: ['media']) so nothing is counted twice; the force never changes the body's velocity by more than the difference to the flow that arrives at it, so a body lighter than the air it moves is not thrown.forces: 'coefficients'(default) keeps the media drag and uses the box for the field only. Water boxes usecoefficients. - Cost:
maxCells(default 40000): a box over it coarsens its cell by 1.25 until it fits and setsdegraded. A box cansleep(the analytic field stays),every: nsteps it every n-th physics step with the time gathered. Measured on the development machine: 0.8–0.9 µs per cell per step up to 16k cells (64²: 3.3 ms, 128²: 14.7 ms), 4 µs per cell at 256² (65k cells, cache limit). Console tiers scale these by their measured CPU ratio; the D7 numbers are set from them. - Tested: divergence under tolerance every step with a body in the way; Bernoulli in a contraction within 5 %; a cylinder at Re 200 sheds at St 0.17–0.23 and has Cd within 20 % of 1.4; a plate at an angle gets drag and lift to the right side; a swung plate pushes the air and the air pushes back; a tower's lee has less than half the wind; a pier makes a wake in water; deterministic.
- Commands:
flowbox.state,flowbox.slice(the inspector view: points with velocity, speed, pressure, solid),flowbox.at,flowbox.wake,flowbox.sleep,edit.physics.flowBoxes.set/remove. Words:(pressure-at x y z),(dynamic-pressure x y z)(½ρv²);flow-atalready reads the box. - Not built:
3dboxes (refused with that message), compressible flow, a pressure-driven roof that comes off (the force is there; the strength link is a scene), the render of the slice in the inspector panel (the command is there).