FERRETGame engine over Babylon.js
Ferret

@ferret/physics-vehicles

ships, submarines and balloons: hulls, ballast, flooding, thrusters, foils and sails.

31 exports · source in packages/physics-vehicles/src

Classes

Airship class

class Airship
readonly vessel: Vessel
readonly spec: AirshipSpec
readonly contract: AirshipContract
readonly engines: Engine[]
readonly cellIds: number[]
readonly mooringAt: Vec3body-frame positions of the mooring point and the ground contacts
crash: Crash | nullthe first crash, or null: a hard landing (closing speed over 4 m/s) or the hull driven 2 m into the ground
elevator: numberthe elevator and rudder commands, -1..1 (positive: nose up, nose to starboard)
rudder: number
helm: { pitch: number | null; heading: number | null }The helmsmen: with a target set, the elevator (pitch) or the rudder (heading, rad from +z toward +x) is driven by a PD law each sub-step, as the two helmsmen of the real ship did. The hull is about neutrally stable in pitch and yaw at cruise (the fin lift slope is tuned for that), so a ship left alone drifts slowly; with the helm held it is steady. Set a target to null to take the wheel.
ventedKg: number
fuelBurnt: numberthe loads in kg: fuel burnt and ballast dropped so far
ballastDropped: number
map(p: Vec3): Vec3A point of the model (X nose to tail, Y up, Z to starboard) in the vessel's body frame. The sign of x is chosen so that the model, as the Babylon glTF importer gives it (z flipped), turns into the body frame by a quarter turn about the vertical and not by a reflection: a mirror would be harmless for a ship symmetric about its centre plane, but the picture and the physics must agree on which engine car is where. (Body x is then the model's port side; the ship is symmetric, only the names say which is which.)
mapDir(p: Vec3): Vec3a direction of the model in the vessel's body frame
constructor(spec: AirshipSpec, contract: AirshipContract, o: AirshipOptions = {})
telegraph(command: number, i?: number)Telegraph: -1 (full astern) .. 1 (full ahead) for engine i (0-based), or for all when i is omitted.
get cruiseCommand()the commanded share of the maximum shaft speed that gives the cruise power: (cruise_rpm / max_rpm), ahead
valve(id: number, open: boolean)Open or shut the manoeuvring valve of a cell (ids from 1, aft to fore); only cells with one respond.
dropBallast(i?: number)Drop the water of a bag (or of all bags) at its release rate.
dropShare(share: number)Drop a share of every bag's present load (0..1).
get ballastKg()
get fuelKg()
cellFill(id: number): numberGas in cell id as a share of the cell's volume now.
get heaviness(): numberthe heaviness now: the mass less the air the cells displace, in kg (positive: heavy, sinking)
get gasMassKg(): numberthe mass of the lifting gas in the cells, kg
moor(anchor: Vec3, length: number)
unmoor()
get moored()
tension: numbertension of the mooring line now, N
setGround(on: boolean)
get position(): Vec3
get altitude()altitude of the hull axis above sea level, m
get airVelocity(): Vec3velocity through the air in the body frame [starboard, up, bow], m/s
get airspeed()
get groundSpeed()
get climbRate()
get heading()
get pitch()pitch (nose up positive) and roll (starboard down positive), rad
get roll()
get turnRate()yaw rate about the vertical, rad/s (positive: nose to starboard)
step(dt: number, h = 0.05)Advance by dt seconds in sub-steps of at most h s.
get groundLoad()Total weight on the ground, N (the sum of the contacts)
reset()Start a run: remember the loads for ballastDropped.

packages/physics-vehicles/src/airship.ts:86

Vessel class

class Vessel
readonly spec: VesselSpec
env: Environment
pos: Vec3world position of the body origin, orientation, velocity of the centre of gravity, angular velocity (world)
q: Quat
v: Vec3
w: Vec3
time: number
readonly tanks: Tank[]
readonly compartments: Comp[]
readonly breaches: Array<BreachSpec & { Cd: number }>
readonly envelopes: Env[]
readonly thrusters: Thr[]
readonly surfaces: Surf[]
crushed: boolean
dynamic: booleanstep the dynamics; false holds the pose (to read the flooding or the forces alone)
onEvent: ((e: VesselEvent) => void) | null
last: { buoyancy: number; weight: number; lift: number; submerged: number; }forces seen in the last step, for readouts
readonly doors: Array<DoorSpec & { open: boolean }>
constructor(spec: VesselSpec, env: Environment = defaultEnvironment())
mass()total mass, centre of gravity and principal inertia now (the body-frame centre the state follows is only moved by a step)
world(p: Vec3): Vec3world position of a body-frame point
body(v: Vec3): Vec3body-frame vector of a world vector
pointVelocity(p: Vec3): Vec3velocity of a body point in the world
hydrostatics(): HydroDisplaced volume, centre of buoyancy and the force of the water on the hull, from the cells against the surface.
get wetShare(): numberShare of the hull volume under water.
get draft(): numberDraft of the hull: the depth of its lowest point under the surface along the up direction (m).
get heel()heel (about the bow axis) and trim (about the beam axis) in radians; positive heel lowers the starboard side
get trim()
get depth()pressure at the gauge, Pa, and the depth of the gauge under the surface, m
get pressure()
get altitude()
get total()
stability(): { draft: number; volume: number; kb: number; kg: number; bm: number; gm: number; gmNumeric: number; freeSurface: number }Stability: draft, volume, KB, KG, BM = I/V and GM for a box hull, with the GM also found by heeling the hull a little.
freeSurfaceLoss(): numberThe metacentric height that free surfaces take: a part-filled compartment lets its water run to the low side as the vessel heels, which acts like a higher centre of gravity by rho_w i / m with i = length x beam^3 / 12 the second moment of its water plane (the formula of the free-surface correction). A full or empty compartment, or one with no beam, takes nothing.
metacentric(phi = 0.03): numberGM from the righting arm at a small heel: GZ / sin φ with the draft re-found so that buoyancy equals weight.
tank(name: string)
fill(name: string, fraction: number)
blow(name: string)Blow the tank with compressed air: it empties (fast).
thruster(name: string)
surface(name: string)
envelope(name?: string)
compartment(name: string)
door(name: string)
setDoor(name: string, open: boolean)Shut or open a watertight door.
breach(b: BreachSpec)
lift(e: Env = this.envelopes[0]): numberLift of an envelope now: (ρ_air − ρ_gas)·V·g, N.
step(dt: number, sub = 1)
windAt(p: Vec3): Vec3the air flow at a world point: the wind of the environment at that height plus the flow field
applyForce(f: Vec3, worldPoint?: Vec3)A force (N, world) at a world point, held for the step: every sub-step of it sees the force, then the list is cleared. Without a point it acts at the centre of gravity.
applyTorque(t: Vec3)A torque (N·m, world) held for the step.
applyImpulse(j: Vec3, worldPoint?: Vec3)An instant push (N·s, world) at a world point: changes the velocity and the angular velocity now.
clearExternal()Forget the forces and torques applied for the step (the capability does it after reading them).
forces(h: number): { F: Vec3; T: Vec3; m: { m: number; cog: Vec3; I: Vec3 } }The vessel's parts over a step of h: pumps, flooding and burners change the mass, then every force on the body is summed about the centre of gravity (weight included). The state is not moved; step integrates it itself, or a rigid-body kernel does (adopt).
adopt(pos: Vec3, q: Quat, v: Vec3, w: Vec3)Take the pose and the velocities from a body a kernel moves (the velocity of the centre of gravity, the angular velocity).
advanceClock(dt: number)After a kernel moved the body: the clock and the events.
get centreWorld(): Vec3world position of the centre of gravity

packages/physics-vehicles/src/vessel.ts:154

Functions

buildAirship function

function buildAirship(spec: AirshipSpec, contract: AirshipContract, o: AirshipOptions = {}): Airship

Build an airship from spec.json and airship.json (parsed).

packages/physics-vehicles/src/airship.ts:421

defaultEnvironment function

function defaultEnvironment: (level?: number) => Environment

packages/physics-vehicles/src/vessel.ts:140

discThrust function

function discThrust(p: number, u: number, rho: number, d: number): number

Thrust of an actuator disc of diameter d taking p watts from the shaft at flight speed u (m/s) in air of density rho: P = T (u/2 + sqrt(u²/4 + T/(2 rho A))).

packages/physics-vehicles/src/airship.ts:75

neutralFill function

function neutralFill(spec: AirshipSpec, contract: AirshipContract, altitude: number, mass = contract.mass.loaded_kg): number

The cell fill (a share of the volume at the pressure and temperature of altitude) at which the ship of mass kg is neutral with the cells just full at that altitude; with that many moles at a lower altitude the cells are slack and the ship is neutral there too.

packages/physics-vehicles/src/airship.ts:415

sailPolar function

function sailPolar(clAlpha: number, stall: number, cd0: number, k: number, alpha: number)

Force of a sail (a surface in the wind) along the boat's heading and across it, for an apparent wind at beta from the bow and the sail set at alpha to it.

packages/physics-vehicles/src/vessel.ts:657

vehicles function

function vehicles(): VehiclesCapability

packages/physics-vehicles/src/index.ts:32

Interfaces

AirshipContract interface

interface AirshipContract
cells: Array<{ id: number; volume_m3: number; centroid: Vec3 }>
mass: { loaded_kg: number; centre: Vec3; groups: Array<{ name: string; kg: number; at: Vec3 }> }
engines: { cars: Array<{ car: string; prop: string; at: Vec3 }> }
surfaces: Array<{ node: string; fin: string; hinge_at: Vec3; hinge_axis: Vec3; max_deflection_deg: number }>
mooring: { at: Vec3 }

packages/physics-vehicles/src/airship.ts:26

AirshipOptions interface

interface AirshipOptions
altitude?: numberaltitude of the hull axis above sea level, m (default 600)
heading?: numberheading of the bow, rad from the world +z toward +x
speed?: numberspeed through the air along the bow, m/s
fuel?: numberthe start state of the loads: fuel and ballast in kg (the design values by default) and the cell fill (the spec's start fill)
ballast?: number
fill?: number
env?: Environment
ground?: booleanground contacts and a mooring line
terrain?: (x: number, z: number) => numberthe ground height at (x, z) in metres; without it the ground is flat at the sea level. With it the hull itself is sampled and a hard touch is a crash.

packages/physics-vehicles/src/airship.ts:34

AirshipSpec interface

interface AirshipSpec

A rigid airship (LZ 129 and its kind) on top of Vessel: many gas cells, ballast bags and fuel as tanks, engines with a telegraph and a propeller, four hinged fins, the air's added mass, the hull's aerodynamics (axial drag, Munk moment, cross-flow drag), a mooring line and the ground. It reads two documents of examples/zeppelin: spec/spec.json (the design) and build/airship.json (the contract the Blender model writes: cell volumes and centroids, mass groups, hinge lines). The model's axes are X nose to tail, Y up, Z to starboard; the vessel's body axes are x starboard, y up, z bow, with the origin on the hull axis under the loaded centre of mass in x.

What is estimated rather than sourced: the added-mass coefficients, the cross-flow drag, the fin aspect ratio and the flap effectiveness, the propeller efficiency split, the reverse-thrust share, the ground contacts.

hull: { length: number; diameter: number; xm_share: number; profile_exponent: number }
cells: { count: number; valve_flow_m3s: number; list: Array<{ id: number; auto_valve: boolean; manoeuvre_valve: boolean }> }
gas: { purity: number; start_fill: number; rho_air: number; rho_he: number }
engines: { count: number; max_w: number; max_rpm: number; cruise_w: number; cruise_rpm: number; gear_ratio: number; sfc_kg_per_kwh: number; prop: { diameter: number; eta: number } }
fins: Array<{ name: string; area: number; span: number; control_area: number; max_deflection_deg: number }>
aero: { cd_volume: number; ref_area_m2: number; v_cruise: number; v_max: number }
tanks: { fuel_capacity_kg: number; fuel_density: number; ballast_capacity_kg: number; ballast_bags: number }
design: { payload_kg: number; fuel_kg: number; fuel_x: number; fuel_y: number; ballast_kg: number; bag_kg: number[]; bag_x: number[]; bag_y: number[] }

packages/physics-vehicles/src/airship.ts:16

BreachSpec interface

interface BreachSpec
name: string
compartment: string
at: Vec3
area: number
Cd?: number

packages/physics-vehicles/src/vessel.ts:31

CompartmentSpec interface

interface CompartmentSpec
name: string
volume: number
at: Vec3
area: number
floor: number
beam?: number
slosh?: boolean | numberlet the water slosh across the beam as a motion: Housner's mass on a spring at the first mode, ω² = (π g / B) tanh(π h / B), with this damping ratio (0.03 when true)

packages/physics-vehicles/src/vessel.ts:26

Crash interface

interface Crash

what the ship hit and how hard (set once: the first crash)

time: number
speed: number
what: string
position: Vec3

packages/physics-vehicles/src/airship.ts:72

DoorSpec interface

interface DoorSpec

A door between two compartments: open, the water between them levels by Torricelli; shut, nothing passes. at is its sill in the body frame (the flow starts above it).

name: string
a: string
b: string
at: Vec3
area: number
open?: boolean
Cd?: number

packages/physics-vehicles/src/vessel.ts:30

EnvelopeSpec interface

interface EnvelopeSpec
name: string
kind: 'hot' | 'gas'
volume: numberm³, the full volume of the envelope
at: Vec3
temperature?: numberhot air: the temperature inside, K
moles?: numberlighter gas: moles inside and kg/mol (it expands with height until the envelope is full, then vents)
molarMass?: number
burner?: numberburner power, W, and the lumped loss to the air, W/K
loss?: number
vent?: numbera vent that replaces this share of the hot air per second with ambient air
dragArea?: numberprojected area for the wind drag, m², and its coefficient
dragCd?: number
dragLinear?: numberlinear damping against the air, N·s/m

packages/physics-vehicles/src/vessel.ts:32

Environment interface

interface Environment
surface: (x: number, z: number, t: number) => numberthe water surface height at (x, z, t)
waterDensity: number
atmosphere: Atmosphere
wind: (height: number) => Vec3wind velocity at a height
flow?: (p: Vec3) => Vec3extra air flow at a world point (the level's flow sources, @ferret/flow); added to wind
seaLevel: numberthe height of the ground/sea level that the atmosphere counts from

packages/physics-vehicles/src/vessel.ts:127

HullSpec interface

interface HullSpec
size: Vec3full size of the box hull
center?: Vec3the centre of the box in the body frame
cells?: [number, number, number]cells along each axis

packages/physics-vehicles/src/vessel.ts:16

Hydro interface

interface Hydro
volume: number
centre: Vec3
force: number
waterline: number

packages/physics-vehicles/src/vessel.ts:152

SurfaceSpec interface

interface SurfaceSpec
name: string
at: Vec3
area: number
hinge: Vec3the hinge axis in the body frame
chord: Vec3direction of the chord from leading to trailing edge when undeflected
medium?: 'water' | 'air'
clAlpha?: numberlift slope per radian (2π·AR/(AR+2) for a finite foil)
stall?: number
cd0?: number
k?: numberinduced drag factor: Cd = cd0 + k·Cl²

packages/physics-vehicles/src/vessel.ts:65

TankSpec interface

interface TankSpec
name: string
volume: number
at: Vec3
density?: number
fill?: number
rate?: number

packages/physics-vehicles/src/vessel.ts:25

ThrusterSpec interface

interface ThrusterSpec
name: string
at: Vec3
axis: Vec3
maxThrust: number
medium?: 'water' | 'air'
maxSpeed?: numberspeed along the axis at which the thrust is gone, m/s (Infinity = a constant thrust)
source?: (u: number) => numbera machine supplies the thrust instead of the curve: (speed along the axis) → N

packages/physics-vehicles/src/vessel.ts:54

VehiclesCapability interface

interface VehiclesCapability extends Capability
readonly vessels: Map<string, VesselEntry>
surface: Environment['surface'] | nullA surface height other than the flat water level (a wave field of L2).
addVessel(name: string, spec: VesselSpec, entity?: Entity, opts?: { body?: boolean; material?: string }): Vesselbody: false keeps the vessel on its own integrator even when the kernel has rigid bodies (it then meets nothing).

packages/physics-vehicles/src/index.ts:24

VesselEntry interface

interface VesselEntry

A vessel on a rigid-body kernel has a body there: Havok moves it and it meets the shore, a quay and other vessels.

vessel: Vessel
entity?: Entity
body?: Body
mass?: number
cog?: Vec3

packages/physics-vehicles/src/index.ts:23

VesselEvent interface

interface VesselEvent
type: VesselEventType
part?: string
time: number

packages/physics-vehicles/src/vessel.ts:143

VesselSpec interface

interface VesselSpec
mass: number
cog?: Vec3centre of gravity of the dry vessel in the body frame, and its principal inertia about it
inertia: Vec3
hull?: HullSpec
tanks?: TankSpec[]
compartments?: CompartmentSpec[]
doors?: DoorSpec[]
addedMass?: Vec3Added mass: the water the hull drags along when it accelerates, as a share of the water it displaces, per body axis (surge, heave, sway as x, y, z). About 0.05 to 0.2 along a slender hull, near 1 across a flat bottom. It slows the vessel's response and lowers the heave frequency, not its weight.
addedInertia?: Vec3Added moment of inertia: the water that turns with the hull, as a share per body axis (roll about z, yaw about y, pitch about x as the axes [x, y, z]) of the moment of the displaced water as a block of the hull's size, rho V_sub (b² + c²) / 12. About 0.3 to 1 in roll for a ship.
airAddedMass?: Vec3Added mass in air (an airship pushes a mass of air about with it, comparable to its own): kg per body axis at sea-level density (surge, heave, sway as x, y, z), scaled with the air density at the altitude. About k·ρ·V of the displaced air: k ≈ 0.05 along a slender hull, ≈ 0.9 across it.
airAddedInertia?: Vec3the air that turns with the hull, kg·m² per body axis at sea-level density (scaled the same way)
envelopes?: EnvelopeSpec[]
thrusters?: ThrusterSpec[]
surfaces?: SurfaceSpec[]
position?: Vec3
orientation?: Quat
velocity?: Vec3
crushDepth?: numbera hull that breaks below this depth, m
hullDrag?: Vec3drag on the hull in the water: quadratic coefficient per axis (½ρ·Cd·A, N/(m/s)²) and linear damping (N·s/m), scaled by the submerged share
hullDamping?: Vec3
angularDamping?: Vec3angular damping in the water, N·m·s/rad per axis, scaled by the submerged share
gravity?: numbergravity on the vessel's own mass, m/s² (9.80665; zero holds a test vessel up)
gauge?: Vec3the point the depth gauge reads (body frame; the centre of gravity when missing)

packages/physics-vehicles/src/vessel.ts:82

Types

VesselEventType type

type VesselEventType = 'breach' | 'flooded' | 'crushed' | 'surfaced' | 'submerged' | 'capsized'

packages/physics-vehicles/src/vessel.ts:142

Constants

AIRSHIP_ESTIMATES const

const AIRSHIP_ESTIMATES: { kAxial: number; kLateral: number; kRotation: number; cdCross: number; finAspect: number; finInterference: number; flapEffect: number; finStall: number; finCd0: number; propProfile: number; reverseShare: number; rpmRamp: number; }

estimated coefficients, one place

packages/physics-vehicles/src/airship.ts:53

G const

const G: 9.80665

A vessel is one rigid body with parts: a hull that displaces liquid (cells against the water surface), ballast tanks, compartments that flood through breaches, gas envelopes, thrusters, control surfaces and sails. It integrates its own six degrees of freedom (the light kernel has no dynamic box and one scalar inertia), reads the water and the air, and reports the readouts a game needs: draft, heel, trim, metacentric height, depth and buoyancy against weight.

Body axes: x to starboard, y up, z to the bow.

packages/physics-vehicles/src/vessel.ts:12

R_UNIVERSAL const

const R_UNIVERSAL: 8.314462618

packages/physics-vehicles/src/vessel.ts:13

VEHICLES_DEFAULTS const

const VEHICLES_DEFAULTS: { waterLevel: number; waterDensity: number; wind: Vec3; }

Assemblies of capability V: ships, submarines and balloons. A vessel is one body with parts (a hull, ballast tanks, compartments, gas envelopes, thrusters, control surfaces and sails) that read the water and the air and write forces. This capability steps them with the world, moves the scene node that shows each one, and answers the commands a game drives them with.

values (each at world / level / zone / entity scope):

  waterLevel    height of the water surface, m                   (0)
  waterDensity  kg/m³                                            (1000)
  wind          wind velocity at all heights, [x, y, z] m/s       ([0, 0, 0])

events: vessel:breach, vessel:flooded, vessel:crushed, vessel:surfaced, vessel:submerged, vessel:capsized, each { vessel, part? }.

packages/physics-vehicles/src/index.ts:20