@ferret/physics-liquids
containers and reservoirs, a height-field water surface, particle liquids, foils, hydraulic circuits (cylinders, valves, pumps, accumulators).
Read the guide: how to use it →
Classes
Capillary class
class Capillaryrise: number | height of the liquid in the tube above the reservoir surface (negative: pushed down, mercury in glass) |
constructor(readonly def: CapillaryDef) | |
equilibrium(l: LiquidMaterial, angle: number, g: number) | |
step(dt: number, net: LiquidNetwork, angle: (liquid: string, solid?: string) => number) |
packages/physics-liquids/src/index.ts:55
Fluid class
class Fluidn: number | |
x: Float64Array | |
v: Float64Array | |
readonly colliders: Collider[] | |
readonly s: number | |
readonly H: number | |
readonly mass: number | |
readonly rho0: number | |
readonly density: number | |
readonly viscosity: number | |
readonly tension: number | |
adhesion: number | |
readonly gravity: Vec3 | |
iterations: number | |
substeps: number | |
readonly budget: number | |
stats: FluidStats | |
time: number | |
constructor(d: FluidDef) | |
add(points: Iterable<Vec3>, velocity: Vec3 = [0, 0, 0]) | Add particles at the points. |
fillBox(min: Vec3, max: Vec3, velocity: Vec3 = [0, 0, 0]) | Fill a box with particles on the lattice. |
fillSphere(c: Vec3, r: number, velocity: Vec3 = [0, 0, 0]) | Fill a sphere (a drop). |
remove(i: number) | |
position(i: number): Vec3 | |
get volume() | Total liquid volume, m³ (particles × the volume each stands for). |
densityOf(i: number) | Density of the particle i at its last solve, kg/m³ (reported in units of the true density, not the lattice's). |
step(dt: number) |
packages/physics-liquids/src/particles.ts:65
HydraulicCircuit class
class HydraulicCircuitreadonly nodes: Map<string, Node> | |
readonly links: Map<string, HydraulicLink> | |
readonly cylinders: Map<string, Cylinder> | |
time: number | |
readonly rho: number | |
readonly bulk: number | |
substeps: number | sub-steps used by the last step |
residual: number | the worst scaled residual and the Newton iterations of the last sub-step (a check on the solve) |
iterations: number | |
constructor(readonly liquid: LiquidMaterial, o: { bulk?: number } = {}) | |
tank(id: string): this | an open tank at the atmosphere (a node tank exists from the start) |
line(id: string, volume: number, pressure = P_AMB): this | a line or junction holding volume m3 of liquid, at pressure Pa |
accumulator(id: string, o: { precharge: number; gas: number; gamma?: number; line?: number; pressure?: number }): this | a gas-charged accumulator on a line: precharge Pa, gas volume at precharge m3, polytropic exponent (1.4 adiabatic, 1 isothermal), plus the line volume |
stored(id: string): number | liquid held in an accumulator at its pressure, m3 |
pump(id: string, node: string, rate: number | (() => number)): this | a constant or time-varying flow into a node (a pump), m3/s |
pressure(id: string): number | |
link(id: string, spec: LinkSpec): HydraulicLink | |
relief(id: string, line: string, o: { crack: number; cdA: number; range?: number; to?: string }): HydraulicLink | a relief valve from line to the tank, set to crack Pa |
setOpen(id: string, v: number) | set the opening of a valve link (a number replaces a function) |
cylinder(id: string, def: CylinderDef): Cylinder | |
handPump(id: string, o: { bore: number; stroke: number; to: string; cdA?: number; from?: string; dead?: number }): HandPump | a hand pump: a small cylinder driven by a lever (0..1), with an inlet check from the tank and an outlet check to to. lever is the position of the piston as a share of the stroke. |
directional(id: string, o: { supply: string; a: string; b: string; cdA: number; tank?: string }): DirectionalValve | a four-way, three-position valve: the spool in -1..1; positive sends the pump to a and b to the tank, negative the other way, zero closes all (the load holds) |
step(dt: number): void | |
liquidVolume(): number | Total liquid in the circuit (compressed and stored), m3: for checks that nothing is made or lost. |
fromTank(): number | Volume that has gone into the tank and come out of it, m3, through the links that touch it. |
packages/physics-liquids/src/hydraulics.ts:82
Link class
class Linkreadonly def: LinkDef | |
flow: number | m³/s, positive from → to |
velocity: number | |
reynolds: number | |
regime: 'none' | 'laminar' | 'turbulent' | 'orifice' | 'pump' | |
dp: number | the driving pressure (Pa) and the liquid that flows |
liquid: string | |
constructor(def: LinkDef) | |
get id() | |
get area() | |
set valve(v: number) | |
get valve() | |
pressureAt(s: number, pIn: number, density: number): number | The pressure at a distance s along a pipe with a profile, by Bernoulli from the inlet pressure pIn: the speed follows the
bore (continuity), and the pressure drops by ½·ρ·(v² − v_in²). Friction is not subtracted: it is the Venturi, not the whole line. |
packages/physics-liquids/src/network.ts:90
LiquidLibrary class
class LiquidLibraryconstructor(initial: Record<string, LiquidMaterial> = EXAMPLE_LIQUIDS) | |
add(name: string, l: LiquidMaterial) | |
get(name: string): LiquidMaterial | |
has(name: string) | |
names() | |
contactAngle(liquid: string, solid?: string): number | Contact angle in degrees against a solid (90° when the liquid has no entry for it and no default). |
packages/physics-liquids/src/liquid.ts:26
LiquidNetwork class
class LiquidNetworkReservoirs joined by links: levels, pressures and flows. No particles; exact where the physics is exact (hydrostatics, Bernoulli, Poiseuille).
readonly reservoirs: Map<string, Reservoir> | |
readonly links: Map<string, Link> | |
readonly discharged: Map<string, number> | liquid that has left the network through open ends, by liquid, m³ |
constructor(readonly liquids: LiquidLibrary, public g = 9.80665) | |
addReservoir(def: ReservoirDef): Reservoir | |
addLink(def: LinkDef): Link | |
removeReservoir(id: string) | |
removeLink(id: string) | |
pressureAt(id: string, y: number): number | Pressure at a world y inside a reservoir's liquid, p = p0 + ρ·g·depth. |
gateForce(id: string, width: number, yBottom: number, yTop: number): { force: number; centreY: number } | Force (N) of the liquid on a vertical rectangular gate of the given width between two world heights, and the depth of its centre of pressure. |
step(dt: number, o: StepOptions = {}) |
packages/physics-liquids/src/network.ts:135
Reservoir class
class Reservoirreadonly id: string | |
liquid: string | |
readonly container: Container | |
readonly position: [number, number, number] | |
volume: number | |
pressure: number | pressure over the surface when the tank is open |
gas?: GasVolume | |
level: number | surface height above the container's bottom (m), with what bodies displace |
displaced: number | the volume that bodies in the tank displace at the current level |
full: boolean | |
empty: boolean | |
discharged: number | |
received: number | |
constructor(def: ReservoirDef) | |
get surfaceY() | |
get headPressure() | pressure of whatever is over the liquid |
get bounds(): { min: [number, number, number]; max: [number, number, number] } | the box the liquid fills, for a medium of the physics step |
packages/physics-liquids/src/network.ts:22
WaterSurface class
class WaterSurfacereadonly nx: number | |
readonly nz: number | |
readonly dx: number | |
readonly origin: [number, number] | |
readonly level: number | |
readonly g: number | |
readonly eta: Float64Array | elevation of the surface above the rest level, per cell |
readonly depth: Float64Array | rest depth per cell |
readonly qx: Float64Array | flow per unit width (m²/s) through the x faces, (nx+1)·nz, and the z faces, nx·(nz+1) |
readonly qz: Float64Array | |
readonly u0: Float64Array | steady current per cell |
readonly v0: Float64Array | |
friction: number | |
readonly nonlinear: boolean | |
time: number | |
lastMs: number | wall-clock cost of the last step, ms (for the inspector) |
constructor(d: SurfaceDef) | |
get maxSpeed(): number | The fastest wave speed on the grid, m/s. |
get maxStep(): number | The largest stable step, s. |
height(x: number, z: number): number | Height of the water surface at a point, m (the rest level where the point is off the grid's cells it clamps to the nearest). |
depthAt(x: number, z: number): number | Depth of the water at a point, m. |
normal(x: number, z: number): Vec3 | Surface normal at a point. |
velocity(x: number, z: number): [number, number] | Velocity of the water at a point: the current plus the flow of the waves, m/s ([x, z]). |
disturb(x: number, z: number, volume: number, radius = 2 * this.dx) | Add (or take away, when negative) a volume of water around a point, spread over a smooth disc of the radius; at the next step. A body that pushes into the water adds the volume it displaces. |
get volume(): number | Total volume above the rest level, m³. |
energy(rho = 1000): number | Energy of the waves, J per unit density of water ρ = 1000 kg/m³. |
field() | The heights and normals handed to a surface shader of @ferret/fx: nothing is drawn here. |
step(dt: number) | Advance by dt, split into steps below the stability limit. |
packages/physics-liquids/src/surface.ts:39
Wick class
class Wickreach: number | how far the liquid has crept into the wick, m |
constructor(readonly def: WickDef) | |
step(dt: number, net: LiquidNetwork, angle: (liquid: string, solid?: string) => number) |
packages/physics-liquids/src/index.ts:73
Functions
adhesionFor function
function adhesionFor: (contactAngleDeg: number) => numberpackages/physics-liquids/src/particles.ts:479
bodySdf function
function bodySdf(b: Body): Collider['sdf']The SDF of a body's shape where it is now (it turns with the body). A hull stands in as its bounding box; a mesh is refused.
packages/physics-liquids/src/fluid.ts:51
boxContainer function
function boxContainer(width: number, depth: number, height: number): Containerpackages/physics-liquids/src/container.ts:28
boxSdf function
function boxSdf: (c: Vec3, half: Vec3) => (x: number, y: number, z: number) => numberA solid box (negative inside it).
packages/physics-liquids/src/particles.ts:26
buildCircuit function
function buildCircuit(d: CircuitDef, liquids: Record<string, LiquidMaterial> = EXAMPLE_LIQUIDS): BuiltCircuitpackages/physics-liquids/src/hydraulics-host.ts:63
capVolume function
function capVolume(r: number, s: number): numberVolume of the part of a sphere of radius r below a height s above its bottom.
packages/physics-liquids/src/water.ts:40
containerOf function
function containerOf(s: ContainerSpec): Containerpackages/physics-liquids/src/index.ts:96
creep function
function creep(u: number, drive: number, gTerm: number, radius: number, viscosity: number, dt: number): numberHow far a liquid has crept after dt in a thin channel of radius R: with the length u, u·du/dt = R²/(8μ)·(drive − g_term·u), which is singular at u = 0 in u but smooth in M = u²: dM/dt = R²/(4μ)·(drive − g_term·√M). drive is the capillary pressure 2γ|cosθ|/R (Pa) and gTerm the weight of the column per metre (ρ·g, or ρ·g·sin α for a tilted wick).
packages/physics-liquids/src/index.ts:48
cylinderContainer function
function cylinderContainer(radius: number, height: number): Containerpackages/physics-liquids/src/container.ts:33
foilForce function
function foilForce(f: FoilDef, v: Vec3, flow: Vec3, rho: number, chordDir: Vec3 = f.chord ?? [1, 0, 0], spanDir: Vec3 = f.span ?? [0, 0, 1]): FoilForceForce on a foil moving with velocity v through a fluid moving with velocity flow (both in the frame of chord and span, which the caller has rotated into place), in a fluid of density rho. The lift is perpendicular to the relative flow and to the span; the drag is along the relative flow.
packages/physics-liquids/src/foil.ts:57
hydraulicForms function
function hydraulicForms(br: FormBridge, cap: HydraulicsCapability): voidpackages/physics-liquids/src/hydraulics-host.ts:181
hydraulics function
function hydraulics(): HydraulicsCapabilitypackages/physics-liquids/src/hydraulics-host.ts:87
jurinHeight function
function jurinHeight(l: LiquidMaterial, angleDeg: number, radius: number, g = 9.80665): numberJurin's law: the rise of a liquid in a tube of radius r, h = 2·γ·cosθ / (ρ·g·r); negative (a depression) when θ is over 90°.
packages/physics-liquids/src/liquid.ts:47
laplacePressure function
function laplacePressure(l: LiquidMaterial, radius: number, surfaces = 1): numberLaplace pressure inside a drop (one surface) or a bubble in the liquid; a soap bubble has two surfaces: pass surfaces = 2.
packages/physics-liquids/src/liquid.ts:57
liquids function
function liquids(): LiquidsCapabilitypackages/physics-liquids/src/index.ts:117
magnus function
function magnus(v: Vec3, omega: Vec3, radius: number, rho: number): Vec3Magnus force on a spinning ball: F = ½ρ·A·Cl·|v|²·(ω̂ × v̂), with Cl rising with the spin ratio r|ω|/|v| and saturating at 0.4. v is the velocity of the ball relative to the fluid.
packages/physics-liquids/src/foil.ts:73
particleLiquid function
function particleLiquid(): FluidCapabilitypackages/physics-liquids/src/fluid.ts:73
planeSdf function
function planeSdf: (normal: Vec3, offset: number) => (x: number, y: number, z: number) => numberpackages/physics-liquids/src/particles.ts:23
profileContainer function
function profileContainer(heights: number[], areas: number[], half?: [number, number]): ContainerAny container from its profile: the cross-section area at a list of heights (increasing from 0), linear between them. A funnel, a bottle, a hull's hold; the table can be built once from a closed mesh by slicing it.
packages/physics-liquids/src/container.ts:53
roomSdf function
function roomSdf: (c: Vec3, half: Vec3) => (x: number, y: number, z: number) => numberA box-shaped room: the fluid is kept inside it.
packages/physics-liquids/src/particles.ts:31
sphereContainer function
function sphereContainer(radius: number): ContainerA spherical tank, or a round flask.
packages/physics-liquids/src/container.ts:39
sphereSdf function
function sphereSdf: (c: Vec3, r: number) => (x: number, y: number, z: number) => numberpackages/physics-liquids/src/particles.ts:24
thinFoil function
function thinFoil(o: { aspect?: number; Cd0?: number; stall?: number; efficiency?: number } = {}): PolarA flat-plate polar with a stall: thin-airfoil lift 2π·α up to the stall angle, then the post-stall flat plate (Cl = sin 2α), drag from the zero-lift drag plus the induced term Cl²/(π·e·AR).
packages/physics-liquids/src/foil.ts:18
tubeSdf function
function tubeSdf: (x0: number, z0: number, radius: number, y0: number, y1: number) => (x: number, y: number, z: number) => numberA vertical tube: a wall of the radius around the axis (x0, z0), the fluid inside; open at both ends between y0 and y1 (the wall ends there).
packages/physics-liquids/src/particles.ts:33
validateCircuit function
function validateCircuit(d: unknown, path = 'hydraulics'): string[]packages/physics-liquids/src/hydraulics-host.ts:31
venturiDrop function
function venturiDrop(density: number, v1: number, a1: number, a2: number): numberPressure drop across a contraction from Bernoulli, Δp = ½·ρ·(v₂² − v₁²), the Venturi effect.
packages/physics-liquids/src/liquid.ts:62
washburnLength function
function washburnLength(l: LiquidMaterial, angleDeg: number, radius: number, t: number): numberWashburn: the distance a liquid has crept into a horizontal pore of radius r after t seconds, L² = γ·r·cosθ·t / (2·μ).
packages/physics-liquids/src/liquid.ts:52
waterSurface function
function waterSurface(): SurfaceCapabilitypackages/physics-liquids/src/water.ts:45
Interfaces
AttachDef interface
interface AttachDefThe hydraulics capability: circuits of the level, stepped every fixed step in the field phase, cylinders that push two bodies apart along an axis, and the commands and forms to drive the valves.
Level block physics.hydraulics: a list of circuits
{ name, liquid?, bulk?, lines: [{ id, volume, pressure? }], accumulators: [{ id, precharge, gas, gamma?, line? }], pumps: [{ node, rate }],
cylinders: [{ id, bore, rod, stroke, mass?, ..., attach?: { a, b, axis } }], links: [{ id, from, to, cdA | laminar, open?, check?, relief? }],
valves: [{ id, supply, a, b, cdA }], handPumps: [{ id, bore, stroke, to }], reliefs: [{ id, line, crack, cdA }] }where a and b of an attached cylinder are the nodes of two bodies (the barrel on a, the rod on b) and axis the extension direction in the frame of a.
a: string | |
b: string | |
axis: Vec3 | |
offset?: number |
packages/physics-liquids/src/hydraulics-host.ts:15
BuiltCircuit interface
interface BuiltCircuitWhat the capability keeps of a circuit: the circuit, its valves and pumps by name.
name: string | |
circuit: HydraulicCircuit | |
valves: Map<string, { id: string; spool: number }> | |
handPumps: Map<string, { id: string; lever: number; handleForce: number }> | |
attached: Array<{ cyl: Cylinder; def: AttachDef }> |
packages/physics-liquids/src/hydraulics-host.ts:55
CapillaryDef interface
interface CapillaryDefCapability L1: liquids as reservoirs and pipes. Liquid in a container has a level that follows from its volume; reservoirs are joined by links (pipes, orifices, valves, pumps, check valves) whose flow comes from the pressure difference, with Hagen–Poiseuille in laminar flow and Darcy–Weisbach in turbulent flow, so levels equalise with the right time constant, two liquids in a U-tube balance ρ₁h₁ = ρ₂h₂, a tank drains by Torricelli. A body put in raises the level by what it displaces. A reservoir is also a medium of the capability D (when it is on), so a body in it floats, sinks and drags by the same law as in a lake. Capillary tubes (Jurin) and wicks (Washburn) take liquid from a reservoir.
values: liquids ({name: LiquidMaterial}) over the examples.
id: string | |
reservoir: string | |
radius: number | |
solid?: string | the solid the tube is made of, for the contact angle |
length?: number | the tube's length above the reservoir surface, m (the liquid stops at the top) |
packages/physics-liquids/src/index.ts:24
CircuitCylinder interface
interface CircuitCylinder extends Omit<CylinderDef, 'load'>id: string | |
attach?: AttachDef |
packages/physics-liquids/src/hydraulics-host.ts:16
CircuitDef interface
interface CircuitDefname: string | |
liquid?: string | LiquidMaterial | |
bulk?: number | |
lines?: Array<{ id: string; volume: number; pressure?: number }> | |
accumulators?: Array<{ id: string; precharge: number; gas: number; gamma?: number; line?: number; pressure?: number }> | |
pumps?: Array<{ node: string; rate: number }> | |
cylinders?: CircuitCylinder[] | |
links?: Array<{ id: string } & LinkSpec> | |
valves?: Array<{ id: string; supply: string; a: string; b: string; cdA: number; tank?: string }> | |
handPumps?: Array<{ id: string; bore: number; stroke: number; to: string; cdA?: number }> | |
reliefs?: Array<{ id: string; line: string; crack: number; cdA: number; range?: number }> |
packages/physics-liquids/src/hydraulics-host.ts:17
Collider interface
interface ColliderL3: liquid as particles, by position-based fluids: a density constraint per particle solved with a fixed number of iterations over a neighbour grid (incompressible, so a still column holds ρ·g·h), XSPH viscosity mapped from the material's viscosity (water to honey), cohesion between particles (Akinci) that rounds drops up, and adhesion to solids that sets the contact angle. Solids are signed distance functions, so static meshes, boxes, spheres and tubes all work the same way, and moving bodies push back through the same call. Fixed step, fixed iteration count and no randomness: a replay is identical.
sdf(x: number, y: number, z: number): number | signed distance to the solid: negative inside it (where a particle may not be), positive outside |
friction?: number | friction against the wall, 0..1 (velocity along the wall lost per contact) |
adhesion?: number | adhesion of this wall as a share of the liquid's cohesion (see adhesionFor); the fluid's default when missing |
onPush?(point: Vec3, impulse: Vec3): void | a moving solid reports what the fluid did to it: the impulse of one correction, N·s (sum over a step / dt is the force) |
velocity?(x: number, y: number, z: number): Vec3 | velocity of the wall at a point (a moving body drags the fluid along); still when missing |
packages/physics-liquids/src/particles.ts:10
Container interface
interface ContainerA container shape for a reservoir: how much liquid fits up to a height. The surface level follows from the volume by inverting volume(h); the cross-section area(h) is what a level rises against.
readonly height: number | inner height, m |
readonly half: [number, number] | horizontal half extents of the footprint (x, z), m: where bodies count as inside, and the box of the water |
area(h: number): number | cross-section at height h, m² |
volume(h: number): number | liquid volume up to height h, m³ |
heightOf(v: number): number | height reached by volume v (clamped to 0..height) |
readonly capacity: number |
packages/physics-liquids/src/container.ts:5
Cylinder interface
interface Cylinderreadonly id: string | |
readonly def: Required<Pick<CylinderDef, 'bore' | 'rod' | 'stroke' | 'dead' | 'mass' | 'coulomb' | 'viscous' | 'leak' | 'damping'>> & CylinderDef | |
x: number | |
v: number | |
cap: string | nodes of the two chambers: id.cap and id.rod |
rod: string | |
force: number | force of the liquid on the piston along the extension, N, and the force the piston gives the load |
atStop: 0 | 1 | -1 | the extension is held at an end stop |
area: number | |
rodSide: number | |
attach?: { read(): { x: number; v: number }; push(f: number): void } | external extension and speed from bodies, when attached |
setLoad(f: ((x: number, v: number, t: number) => number) | undefined): void | the load given by bodies is the one that moves the piston: set mass: 0 and attach |
packages/physics-liquids/src/hydraulics.ts:65
CylinderDef interface
interface CylinderDefbore: number | piston bore and rod diameter, m |
rod: number | |
stroke: number | stroke, m |
dead?: number | dead volume in each chamber, m3 (default a tenth of the swept volume) |
mass?: number | moving mass (piston and rod), kg; 0 with attach: the bodies carry the mass |
coulomb?: number | seal friction: Coulomb N and viscous N·s/m |
viscous?: number | |
damping?: number | damping of the liquid column as a share of critical (default 0.15): the bulk viscosity of the oil and the give of the hoses take the ringing out of the load on its oil spring |
leak?: number | leak across the piston from the cap side to the rod side, m3/s per Pa |
x?: number | starting extension, m |
load?: (x: number, v: number, t: number) => number | a force on the rod along the extension (a load, gravity, a spring), N, from the extension and speed |
packages/physics-liquids/src/hydraulics.ts:14
DirectionalValve interface
interface DirectionalValveid: string | |
spool: number |
packages/physics-liquids/src/hydraulics.ts:359
Drain interface
interface Drain extends DrainDeftaken: number |
packages/physics-liquids/src/fluid.ts:35
DrainDef interface
interface DrainDefinto?: Reservoir | the reservoir that takes the liquid back; without one the liquid leaves the scene |
sdf: (x: number, y: number, z: number) => number | the region that swallows particles: positive outside it, negative inside (an SDF) |
packages/physics-liquids/src/fluid.ts:29
Floater interface
interface Floater extends FloaterDefvolume: number |
packages/physics-liquids/src/water.ts:26
FloaterDef interface
interface FloaterDefbody: Body | |
radius: number | radius of the sphere that stands for the body's displacement, m |
Cd?: number | drag coefficient against the water's velocity, 1 for a rough sphere |
push?: boolean | false: the body does not push the water |
packages/physics-liquids/src/water.ts:17
FluidCapability interface
interface FluidCapability extends Capabilityreadonly fluids: Map<string, Fluid> | |
add(name: string, def: FluidDef): Fluid | |
couple(name: string, body: Body, opts?: { friction?: number; adhesion?: number }): Collider | A kernel body becomes a moving solid of the liquid; a dynamic one is pushed back. |
pour(name: string, def: PourDef): Pour | |
drain(name: string, def: DrainDef): Drain | |
view(name: string): FluidView |
packages/physics-liquids/src/fluid.ts:40
FluidDef interface
interface FluidDefspacing: number | particle spacing at rest, m |
density?: number | kg/m³ |
viscosity?: number | dynamic viscosity, Pa·s |
surfaceTension?: number | N/m: drives the cohesion between particles |
adhesion?: number | adhesion to walls as a share κ of the liquid's own cohesion (0 beads up, 1 spreads; see adhesionFor for a contact angle) |
iterations?: number | |
substeps?: number | |
gravity?: Vec3 | |
budget?: number | most particles the solver will step; beyond it, the solver reduces its iterations and says so |
packages/physics-liquids/src/particles.ts:40
FluidStats interface
interface FluidStatsparticles: number | |
iterations: number | |
substeps: number | |
degraded: boolean | |
neighbours: number | |
ms: number |
packages/physics-liquids/src/particles.ts:58
FluidView interface
interface FluidViewWhat a renderer of @ferret/fx draws: positions and speeds of the particles, and the spacing (a particle's size).
count: number | |
spacing: number | |
positions: Float32Array | |
speeds: Float32Array |
packages/physics-liquids/src/fluid.ts:38
FoilDef interface
interface FoilDefpolar: Polar | |
area: number | planform area, m² |
chord?: Vec3 | unit vectors in the body frame: along the chord (leading edge forward) and along the span |
span?: Vec3 |
packages/physics-liquids/src/foil.ts:34
FoilForce interface
interface FoilForceforce: Vec3 | force on the foil, N, in the same frame as the inputs |
lift: number | |
drag: number | |
alpha: number | angle of attack, rad |
packages/physics-liquids/src/foil.ts:43
FormBridge interface
interface FormBridgeThe words of the language for hydraulics: (spool "circuit" "valve" :extend|:hold|:retract|number), (valve "circuit" "link" x), (cylinder-position "circuit" "cyl"), (cylinder-force "circuit" "cyl"). define is the bridge's define.
define(name: string, sig: { args: string[]; returns?: string }, fn: (ctx: unknown, ...args: any[]) => unknown): unknown |
packages/physics-liquids/src/hydraulics-host.ts:180
HandPump interface
interface HandPumpid: string | |
lever: number | |
last: number | |
area: number | |
node: string | |
handleForce: number | |
delivered: number |
packages/physics-liquids/src/hydraulics.ts:358
HydraulicLink interface
interface HydraulicLinkid: string | |
spec: LinkSpec | |
a: Node | |
b: Node | |
flow: number | |
moved: number | |
open: () => number |
packages/physics-liquids/src/hydraulics.ts:63
HydraulicNodeInfo interface
interface HydraulicNodeInfoid: string | |
pressure: number | |
kind: string |
packages/physics-liquids/src/hydraulics.ts:37
HydraulicsCapability interface
interface HydraulicsCapability extends Capabilityreadonly circuits: Map<string, BuiltCircuit> | |
add(def: CircuitDef): BuiltCircuit | |
remove(name: string): void |
packages/physics-liquids/src/hydraulics-host.ts:81
LinkDef interface
interface LinkDefid: string | |
from: string | 'air' | |
to: string | 'air' | |
fromElevation: number | world y of each end of the pipe |
toElevation: number | |
diameter: number | pipe bore (m) and length (m); an orifice ignores the length |
length?: number | |
roughness?: number | absolute roughness of the wall (m) |
loss?: number | sum of minor-loss coefficients (bends, entrance) |
valve?: number | 0..1 open fraction of a valve |
pump?: { pressure?: number; rate?: number } | pump: adds pressure (Pa) to the flow from → to, or forces a rate (m³/s) |
check?: boolean | a check valve lets liquid pass from → to only |
orifice?: { cd?: number; area?: number } | a sharp opening instead of a pipe: Torricelli with a discharge coefficient |
main?: { liquid: string; pressure: number } | a main (a supply at pressure) in place of from |
profile?: Array<{ s: number; diameter: number }> | pipe diameter along the length, for pressure taps: [{s, diameter}], s from 0 to length |
packages/physics-liquids/src/network.ts:62
LinkEnd interface
interface LinkEndreservoir: string | 'air' | |
elevation: number |
packages/physics-liquids/src/network.ts:60
LinkSpec interface
interface LinkSpecfrom: string | |
to: string | |
cdA?: number | discharge coefficient times the flow area, m2 (the orifice law Q = cdA·sqrt(2·dp/rho)) |
laminar?: number | a laminar leak instead of an orifice: m3/s per Pa |
open?: number | (() => number) | 0..1 now (a number, or a function of time for a valve driven from outside) |
check?: boolean | passes only from from to to |
relief?: { crack: number; range?: number } | a relief valve: opens from crack Pa over range Pa (of the pressure at from) |
packages/physics-liquids/src/hydraulics.ts:50
LiquidMaterial interface
interface LiquidMaterialA liquid as data: the same record for a tank, a pipe, a wick and a splash.
density: number | kg/m³ |
viscosity: number | dynamic viscosity, Pa·s |
surfaceTension: number | N/m |
compressibility?: number | 1/Pa; 0 = incompressible |
contactAngle?: Record<string, number> | degrees against a solid material by name; default for the rest (water on glass is near 0°, mercury on glass near 140°) |
henry?: Record<string, number> | Henry's constants for dissolved gases by species, mol/(m3·Pa) at 298 K: the dissolved amount at equilibrium is k·(partial pressure) per m3 of liquid (water's come from the aqueous set of physics-chemistry, which also holds the temperature dependence) |
packages/physics-liquids/src/liquid.ts:3
LiquidsCapability interface
interface LiquidsCapability extends Capabilitynet: LiquidNetwork | |
liquids: LiquidLibrary | |
tubes: Map<string, Capillary> | |
wicks: Map<string, Wick> | |
addReservoir(host: PhysicsHost | null, def: ReservoirDef, asMedium?: boolean): Reservoir | |
addTube(def: CapillaryDef): Capillary | |
addWick(def: WickDef): Wick | |
advance(dt: number, g: number, host?: PhysicsHost | null): void | Advance by dt with a given gravity (also what the capability's step does). |
packages/physics-liquids/src/index.ts:105
Polar interface
interface PolarLift and drag of a wing, sail, keel, fin or rudder from coefficients of the angle of attack, against the local flow. This is the Bernoulli effect as a game uses it, not a solved flow around the shape.
Cl(alpha: number): number | lift coefficient at an angle of attack α, rad (odd in α for a symmetric foil) |
Cd(alpha: number): number | drag coefficient (even in α) |
packages/physics-liquids/src/foil.ts:7
Pour interface
interface Pour extends PourDefowed: number | |
poured: number |
packages/physics-liquids/src/fluid.ts:27
PourDef interface
interface PourDeffrom?: Reservoir | the L1 reservoir the liquid comes from; without one the pour makes liquid (a tap on the mains) |
at: Vec3 | centre of the nozzle and the velocity the liquid leaves it with |
velocity: Vec3 | |
radius: number | nozzle radius, m |
rate: number | m³/s |
packages/physics-liquids/src/fluid.ts:16
ReservoirDef interface
interface ReservoirDefid: string | |
liquid: string | |
container: Container | |
position?: [number, number, number] | the middle of the container's bottom, world metres |
volume?: number | initial liquid volume (m³), or initial level (m) |
level?: number | |
pressure?: number | pressure over the surface (Pa); the atmosphere unless the tank is closed |
closed?: boolean | a closed tank has a gas head whose pressure follows the volume it has left (isothermal, p·V = const) |
packages/physics-liquids/src/network.ts:7
StepOptions interface
interface StepOptionsg?: number | gravity (m/s²) |
displacement?: (r: Reservoir, surfaceY: number) => number | volume that bodies displace in a reservoir whose liquid surface is at y |
emit?: (name: string, data: Record<string, unknown>) => void | called for events: 'level', 'empty', 'full', 'overflow' |
packages/physics-liquids/src/network.ts:125
SurfaceCapability interface
interface SurfaceCapability extends Capabilityreadonly surfaces: Map<string, WaterSurface> | |
readonly floaters: Map<Body, Floater> | |
add(name: string, def: SurfaceDef): WaterSurface | |
float(def: FloaterDef): Floater | |
height(x: number, z: number): number | null | Height of the water at a point (the first surface that covers it), or null off every surface. |
cost_ms(): number | Wall-clock cost of the surfaces' last updates, ms. |
packages/physics-liquids/src/water.ts:28
SurfaceDef interface
interface SurfaceDefL2: the surface of a lake, a harbour or a river as a height field on a grid, by the linearised shallow-water equations on a
staggered grid (heights at cell centres, flows through the faces), stepped forward-backward so it is stable up to a Courant number
of about 0.7. Waves travel at √(g·h) in the local depth, reflect from the banks and the walls, shoal in shallow water and are
eaten by a sponge at the open edges. A steady current carries the waves and anything floating on them. The grid is not a flow solver
around bodies: bodies push it with disturb and read it with height and velocity.
nx: number | cells along x and z |
nz: number | |
dx: number | cell size, m |
origin?: [number, number] | world position of the corner (min x, min z) of the grid |
level?: number | height of the rest surface, m |
depth?: number | ((x: number, z: number) => number) | depth of the water at rest, m; a number or a function of the world x, z (a beach is a depth that reaches 0) |
current?: [number, number] | ((x: number, z: number) => [number, number]) | steady current, m/s; a vector or a function of x, z |
friction?: number | linear friction on the flow, 1/s |
open?: Array<'minX' | 'maxX' | 'minZ' | 'maxZ'> | cells of sponge at the edges named here (open water); the other edges are walls |
spongeCells?: number | |
gravity?: number | |
nonlinear?: boolean | keep the momentum advection of the shallow-water equations (the flow carries its own momentum: u ∂q/∂x...): a wave of finite height then travels at about √(g·h)·(1 + 3a/2h), so crests catch the troughs and a front steepens. Off: the linear waves, cheaper and unconditionally gentle. |
packages/physics-liquids/src/surface.ts:10
WickDef interface
interface WickDefid: string | |
reservoir: string | |
poreRadius: number | pore radius, m |
solid?: string | |
area?: number | cross-section of the wick, m², and the fraction of it that is pore |
porosity?: number | |
length?: number | length of the wick, m |
angle?: number | angle from horizontal in degrees: 0 creeps along a level wick, 90 straight up against gravity |
packages/physics-liquids/src/index.ts:31
Types
ContainerSpec type
type ContainerSpec = | { kind: 'box'; size: [number, number, number] } | { kind: 'cylinder'; radius: number; height: number } | { kind: 'sphere'; radius: number } | { kind: 'profile'; heights: number[]; areas: number[]; half?: [number, number] }packages/physics-liquids/src/index.ts:91
Constants
EXAMPLE_LIQUIDS const
const EXAMPLE_LIQUIDS: Record<string, LiquidMaterial>packages/physics-liquids/src/liquid.ts:18
FLUID_DEFAULTS const
const FLUID_DEFAULTS: { budget: number; }Capability L3: particle liquids stepped with the world. A kernel body joins a liquid as a solid that moves (the liquid takes its velocity at the wall) and is pushed back (the pressure on it is an impulse on the body, so a ball floats, a paddle splashes). An L1 reservoir stays the truth of the volume (section 8, decision 7): a pour takes its volume out as particles, a drain puts what reaches it back, and nothing is made or lost on the way.
values (each at world / level / zone / entity scope):
budget most particles stepped at full quality per liquid (6000)packages/physics-liquids/src/fluid.ts:14
P_ATM const
const P_ATM: 101325packages/physics-liquids/src/network.ts:5
SURFACE_DEFAULTS const
const SURFACE_DEFAULTS: { rate: number; waterDensity: number; }Capability L2: water surfaces stepped with the world at a lower rate, and the bodies that float on them. A floater gets buoyancy from the volume of its sphere below the surface at its own position (waves lift and tilt it), drag against the water's velocity (the current and the flow of the waves carry it) and pushes the water by the volume it displaces.
values (each at world / level / zone / entity scope):
rate surface updates per second (30)
waterDensity kg/m³ (1000)The surface of a harbour is read by others through height(x, z); vehicles().surface = water.height makes ships ride the waves.
packages/physics-liquids/src/water.ts:15