wind
One wind for the scene, and provinces that bend it. Pure — no DOM, no Babylon — so the composition rule can be tested without a sky.
Set windSpeed and windBearingDeg (and windGust) on <tosi-b3d> and the
consumers take it, so they cannot disagree (clouds streaming north-east over
water whose waves run south). b3d-clouds,
b3d-water and b3d-ambient have a wind
attribute: 'scene' (the default) follows the scene's wind when there is one,
'own' uses the element's own wind attributes. The
cloud deck drifts with the scene's wind whenever there is
one.
Demo
One dial, and the clouds and the water agree. Set wind to 'own' on either
and it falls back to its own attributes.
import { b3d, b3dSun, b3dSkybox, b3dLight, b3dWater, b3dClouds, angle3d, slider3d, label3d } from 'tosijs-3d'
import { orbitCam } from 'tosijs-3d/demo-utils'
let scene = null
const panel = () => [
label3d({ text: 'Weather' }),
angle3d({
label: 'bearing',
value: 45,
size: 150,
handleChange: (v) => { if (scene) scene.windBearingDeg = v },
}),
slider3d({
label: 'speed', value: 6, min: 0, max: 20, step: 0.5, showValue: 'always',
handleChange: (v) => { if (scene) scene.windSpeed = v },
}),
slider3d({
label: 'gust', value: 0.3, min: 0, max: 1, step: 0.05, showValue: 'always',
handleChange: (v) => { if (scene) scene.windGust = v },
}),
]
scene = b3d(
{
style: 'width:100%;height:100%',
scenePanelOpen: true,
scenePanel: panel,
// ONE wind. Clouds and water both read it.
windSpeed: 6,
windBearingDeg: 45,
windGust: 0.3,
sceneCreated(el) {
orbitCam(el, { alpha: -1.2, beta: 1.32, radius: 120, target: [0, 30, 0] })
},
},
b3dSun({}),
b3dSkybox({ timeOfDay: 15 }),
b3dLight({ intensity: 0.4 }),
// Low and close, so the drift is legible in a small preview.
b3dClouds({ coverage: 0.6, altitude: 60, spread: 300, size: 40, count: 24 }),
b3dWater({ y: 0, waterSize: 200, waveHeight: 0.5, twoSided: true })
)
preview.append(scene)
.preview { height: 100%; }
Cartesian inside, degrees at the edge
Stored and composed as a vector in world XZ. Authored as windSpeed and
windBearingDeg, per this library's degrees convention, converted at the edge.
That is not a formatting preference — it is the whole composition rule. Bearings cannot be summed (they would need a weighted circular mean), but vectors can:
| a province that... | contributes | and the result |
|---|---|---|
| funnels a valley | a vector along the valley | sums |
| shelters a lee | a vector opposing the base | sums; the magnitude falls out |
| curls round a headland | a crosswise vector | sums; the DIRECTION falls out |
The case that looked like a paradox — two provinces each "turning the wind 90°" — is not one. Two equal perpendicular contributions give a 45° resultant at √2 the magnitude, which is right, and free. It only looks paradoxical if you average angles, and nobody would if the type is a vector.
So wind uses the same rule the existing scalar climate channels use: temperature and water SUM, and so does this. A vector layer is just three scalar sums that happen to travel together — which also answers whether the province architecture generalises past scalars. It does, unchanged.
Bearing is where the wind is going
Not where it comes from. Meteorology says a "northerly" blows FROM the north,
and that convention exists for forecasting rather than for rendering: every
consumer here wants to know which way the clouds drift and which way the waves
run. Naming it bearing rather than direction is the hint, and it matches the
sense angle3d dials — north is +Z, and it grows clockwise.
The neutral contribution is ZERO, not 0.5
The scalar climate channels are BIPOLAR over 0..1 with 0.5 meaning "leave
the base alone", because a curve has to be able to say "less than the base" and
a curve's range is closed. A vector has no such problem: the neutral
contribution is the zero vector, which needs no midpoint convention at all.
Gusts are a curve over time, not noise you cannot reproduce
gustAt takes an explicit t, so the same second of the same scene gives the
same gust — the determinism rule every model here follows. It is separate from
the steady wind on purpose: a consumer that wants a dead-steady breeze simply
does not call it.