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.
Today b3d-clouds has windX/windZ, b3d-water has
windForce/windDirectionX/windDirectionY, and ambient-leaves has nothing
at all. So an author sets the wind three times, in three spellings, and they
can silently disagree — clouds streaming north-east over water whose waves run
south. That is the same class of defect as three drifting copies of the theme
table, which is what w3d-theme exists to prevent.
Demo
One dial, and the clouds and the water agree. Turn wind off on either and it
falls back to its own attributes — which is what every scene did before this
existed.
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. The first draft of this design stored speed + bearing and concluded that bearings could not be summed, needing a weighted circular mean. Tonio: "Vectors sum fine don't they?" They do, and the difficulty was an artifact of the representation:
| 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.