The sky and the weather are parts, like the land. What made them look special is two general ways of placing a part that other things need too: by direction (the sun, moons and stars, which walking never moves) and round the camera (a sky dome, the rain near the eye, which follow the camera's position). There is no background color in the model: what lies behind everything is whatever the designer draws there. Weather is world state (a record the world keeps and fades between presets), drawn by parts that read it. Falling rain and snow follow the eye and, today, fall through roofs; a storm placed in the world and rain blocked like shadows are designs still to be built. This page sets out each and how it is written.
Words used on this page:
- The viewer
- The camera drawing the picture, not the player. Looking through a far-seeing stone or a portal, the viewer is the projected eye.
- Frame kind
- How a part's place is given: placed (from a parent), round the camera (position from the camera drawing the picture, rotation from the world), by direction (Frames).
- Crossfade
- A blend from one state to another over a time, every number moving together.
- Froxel fog
- Fog computed in a grid of small cells filling the view (frustum voxels), lit by the sun.
- Lux, nits
- Lux is the light that falls on a surface; nits is the brightness of a surface or of the sky seen from the ground.
How you write it
The sky, placed by direction and round the camera
field latitude: angle = 45 deg // settings of the root that the sky package reads
field date: count = 288, within: 1 ..= 365 // the day of the year: 15 October
field moonLit: fraction = 0.85 // how much of the moon's disc is lit; a number, not worked out from the date
part sky: skyDome(atmosphere: preetham(turbidity: 2.5), sun: sunFace, moon: moonFace, weather: weather,
fogReach: 2400 m, fogAnisotropy: 0.3),
at: around(camera, rotation: world)
part sun: sunLight(direction: sunDirection(latitude: latitude, day: date, hour: hour(now(world))),
color: sky.sunColor, lux: sky.sunLux * weather.sun)
part sunFace: sunDisc(direction: sun.direction) // the sun as seen: its own part, beside its light
part moon: sunLight(direction: moonDirection(sun: sun.direction), color: color(0.80, 0.92, 1.10),
lux: sky.moonLux * weather.sun)
part moonFace: moonDisc(direction: moon.direction, sun: sun) // the sun gives it its phase
part stars: starfield(latitude: latitude, hour: hour(now(world))), at: around(camera, rotation: world)
The numbers are an example's: 45 degrees north, day 288 (15 October), turbidity 2.5, a waxing gibbous moon 85 per cent lit. The names of the parts are the world's; the calls, skyDome, sunLight, sunDisc, moonDisc, starfield, sunDirection, moonDirection and preetham, are the sky package's spellings (The vocabulary). latitude and date are fixed fields of the root, settings like the day length (Fields): the sky package reads them by those names, and the world's sun, stars and dusk all use the one pair, so they cannot disagree. turbidity: is preetham's option, how hazy the air is (2 to 6; 2.5 is clean sea air).
What the sky package answers. The sky part answers three numbers from the package's model of the air, so a world does not repeat its figures. sky.sunLux is the sun's light on a surface facing it, worked out as the sun's light crosses the air: 128 000 lux above the air, dimmed by the length of air in the way, by different amounts for red, green and blue (the air's scattering, the haze set by the turbidity, and ozone), and faded to nothing as the sun passes from 1 degree above the horizon to 1 below. sky.sunColor is the color of that light, which reddens as the sun sinks. sky.moonLux is the moon's: 0.25 lux for a full moon times the lit share squared (a half moon gives far less than half the light), coming in as the sun goes below 2 degrees under the horizon and full by 7 degrees under. The weather's sun share then dims the light of either (weather.sun, below).
The sun and the moon are each two parts. A light (sunLight) throws light on the world; a disc (sunDisc, moonDisc) is what the eye sees in the sky. They are placed alike but are different things: the disc is drawn in the sky's one draw, the light is the renderer's. moonDisc(direction:, sun:) takes the sun so that the bright side of the moon faces it; how much of the moon is lit is the root's moonLit. The package does not work the lit share out from the date (its moon is always up at night, a deliberate cheat), so the world states it. moonDirection(sun:) and sunDirection(latitude:, day:, hour:) are the names a world writes; they bind the package's moonDirectionFor(sun) and sunDirectionAt(latitude, declination, hour) (the declination worked out from the day). starfield(latitude:, hour:) turns the stars about the celestial pole, which stands latitude above the north horizon, 15 degrees an hour.
Dusk and dawn: what the sky answers
sky.duskHour // the hour of the day the sun has set: 17.475 (17:28) in the example
sky.dawnHour // the hour it has risen: 6.525 (6:31)
field dayLength: duration = sim(24 min) // a world setting: how long a day of world time lasts
on(created, {
let h = hour(now(world));
set(lampLevel, if h >= sky.duskHour || h < sky.dawnHour { 1 } else { 0 });
})
The sky answers when dusk and dawn fall: sky.duskHour and sky.dawnHour, the hours of the world's day clock at which the sun stands 0.84 degrees below the horizon. That is the moment its top edge meets the horizon, allowing for the bending of its light by the air (refraction): about 0.57 degrees for the bending and 0.27 for the sun's half-width. They are worked out from the world's date and latitude by the same model that places the sun. On 15 October at 45 degrees north they are 17.475 and 6.525, so the night is 13 of the day's 24 hours. The dusk and dawn messages come when the day clock passes these hours (Events), and content that asks "is it night now?" asks the sky rather than typing the hours again, so a change of date or latitude moves both together. How fast the day clock runs is the world's day length, a setting: a 24-minute day, as in the example, runs the world clock 60 times as fast as the scene's (Values: the day length).
The weather: a record, presets, a fade
field clear: weatherState = weatherState(cloudLow: 0.04, cloudHigh: 0.1)
field fair: weatherState = weatherState(cloudLow: 0.42, cloudHigh: 0.5)
field cloudy: weatherState = weatherState(cloudLow: 0.74, cloudHigh: 0.65, cloudLight: 0.92, skyGray: 0.3, skyDim: 0.95,
sun: 0.55, fog: 0.0002, ambient: 0.96, tint: color(0.98, 1, 1.03), ev: 0.35)
field overcast: weatherState = weatherState(cloudLow: 1, cloudHigh: 0.95, cloudLight: 0.8, skyGray: 0.9, skyDim: 0.85,
sun: 0.05, fog: 0.0008, ambient: 0.9, tint: color(0.97, 0.99, 1.03), ev: 1.3)
field rain: weatherState = weatherState(cloudLow: 1, cloudHigh: 1, cloudLight: 0.55, skyGray: 0.95, skyDim: 0.5,
sun: 0, fog: 0.0016, ambient: 0.72, tint: color(0.92, 0.97, 1.06), ev: 1.3, rain: 1)
field snow: weatherState = weatherState(cloudLow: 1, cloudHigh: 1, cloudLight: 0.9, skyGray: 0.95, skyDim: 0.9,
sun: 0.02, fog: 0.0013, ambient: 0.95, tint: color(0.99, 1, 1.05), ev: 1.3, snow: 1)
field presets: List[weatherState] = [clear, fair, cloudy, overcast, rain, snow]
field weatherIndex: count = 1, mutable: true // which preset the weather is heading for: fair
field weather: weatherState = fair, mutable: true
action(nextWeather, kind: once, suggestKey: "T")
on(action(nextWeather), {
set(weatherIndex, (weatherIndex + 1) % presets.count); // after snow, clear again
start(transition(weather, to: presets[weatherIndex], over: sim(12 s), ease: smoothstep, from: current));
})
Six presets, each a weatherState, a record of fifteen numbers (below). A change is a 12 s crossfade on the simulation clock, smoothstep-eased, from wherever the blend is now. A transition may target a record whose fields are all numbers, colors or places, and then each field fades together (Animators: kinds); the weather's fifteen numbers are numbers and one color, so they do. The world declares the action itself, action(nextWeather, kind: once, suggestKey: "T"): a key that cycles the weather for testing. carries: and to: may be left out of a declaration whose action carries nothing and is not aimed at anyone (Visitors: declaring an action).
The weather record
Every field has a type, and the record is declared once, with its defaults (Values: records); a preset names only what differs from the defaults.
| Field | Type, default | What it is |
|---|---|---|
cloudLow, cloudHigh | fraction, required | how much of the sky the low layer, and the high one, cover |
density | fraction = 1 | how opaque the clouds are (a multiplier on the layers' own) |
cloudLight | fraction = 1 | how much light the clouds are lit with: 1 as in a fair day, less under a dark sky |
skyGray | fraction = 0 | how far the blue sky is drained to gray; it also hides the stars and the moon |
skyDim | fraction = 1 | how bright the sky is left |
sun | fraction = 1 | how much of the sun's direct light (and the moon's) gets through, and how much of the sun's disc shows |
fog | number = 0 | the fog's thickness for each meter of sight (0.0016 is a thick murk); under 0.00001 there is none |
ambient | number = 1 | the scale of the sky's fill light, 1 as in a fair day |
tint | color = white | a tint on that fill light, channel by channel; a channel may exceed 1 (the rain's blue is 1.06) |
ev | number = 0 | stops by which the exposure is opened up (the camera opens under a dull sky), added to the exposure curve on Lights |
rain, snow | fraction = 0 | how hard it rains and snows |
That is fifteen numbers: tint is three. weather is the one field the world changes; the sky dome reads it through its weather: option.
Parts that read the weather
part clouds: cloudLayers(heights: [1800 m, 7500 m], scales: [2500 m, 9000 m], opacities: [0.96, 0.38])
derive(clouds.cover, [weather.cloudLow, weather.cloudHigh])
derive(clouds.density, weather.density)
derive(clouds.light, weather.cloudLight)
field start: moment = now(sim) // when the world began: the clouds are unmoved then
derive(clouds.offset[0], wind.at(1800 m) * (now(sim) - start)) // one for each layer
derive(clouds.offset[1], wind.at(7500 m) * (now(sim) - start))
part rainNear: falling(kind: rain, radius: 9 m, above: 6 m, fall: 8 m/s, rate: rate(2600, per: sim(1 s)),
life: sim(1.5 s), spread: 2 deg, size: 2.8 cm, stretch: 0.07,
color: color(0.78, 0.82, 0.88), opacity: 0.5,
push: rate(1.2 m/s, per: sim(1 s)) * wind.direction),
at: around(camera, rotation: world)
derive(rainNear.strength, weather.rain)
part snowNear: falling(kind: snow, radius: 10 m, above: 6 m, fall: 1 m/s, rate: rate(260, per: sim(1 s)),
life: sim(9 s), spread: 20 deg, size: 7 cm, drag: 1.2, flutter: 0.12,
color: color(0.95, 0.96, 1), opacity: 0.85,
push: rate(1.44 m/s, per: sim(1 s)) * wind.direction),
at: around(camera, rotation: world)
derive(snowNear.strength, weather.snow)
Clouds. cloudLayers takes the height of each layer, how many meters of ground one unit of its noise covers (scales:, which sets how big the clouds are) and the most of the sky the layer hides (opacities:, 0.96 for the low layer, 0.38 for the thin high one, each times the weather's density). Their cover, density and light follow the weather by the three derive lines. The light a cloud takes also comes from the sun's color through the high air, held until the sun is 6 degrees below the horizon so clouds redden after the ground does: that is the sky package's. Each layer is carried by the wind. The offset is the wind's velocity at the layer's height times the time since the world began, a velocity times a duration, which is a length (Values: units), so a paused or wound clock puts the clouds back where they were.
Fog and the dome's own parts. The sky dome reads the weather itself (below): its weather: option is how the engine knows the weather. It drives the sky's gray and dim, the sun's and moon's discs, the stars, the haze over the horizon, the fog and the ambient light.
What falling takes
| Option | Type | What it is | Rain, snow |
|---|---|---|---|
kind | rain or snow | what falls | |
radius | meters | the disc the drops are born over, round the eye | 9 m, 10 m |
above | meters | how far above the eye the disc lies | 6 m, 6 m |
fall | speed | how fast it falls (for snow, the speed it settles to) | 8 m/s, 1 m/s |
rate | rate | drops born each second at full strength | 2600, 260 |
life | duration | how long one drop lives | 1.5 s, 9 s |
spread | angle | how far from straight down a drop is launched | 2 deg, 20 deg |
size | meters | the width of a drop or flake | 2.8 cm, 7 cm |
stretch | number = 0 | how far a drop is drawn out along its path | 0.07, 0 |
color | color | the drop's color, lit by the sky as the smoke is | (0.78, 0.82, 0.88), (0.95, 0.96, 1) |
opacity | fraction | how much of a drop shows | 0.5, 0.85 |
drag | number = 0 | how fast a drop's sideways and launch speed die away | 0, 1.2 |
flutter | number = 0 | a wobble, for a flake | 0, 0.12 |
push | a rate of velocity | the sideways push each drop feels: a speed that changes this much each second, along a direction | 1.2 m/s each second, 1.44 |
blockedByRoofs | bool = false | (to be built) whether whatever is above stops the fall, found as a shadow is |
The part's field strength, 0 to 1, scales the rate, so half strength is half the drops; at 0.02 or less nothing falls. The engine keeps a small random spread on each drop's life, speed and size, a quick fade in and out over its life and a slight cooling, so those are not options. Each drop is a formula of its age; nothing is simulated or stored.
The wind, as the world states it
field wind: wind = wind(direction: east, speeds: curve([(0 m, 0.5 m/s), (1800 m, 7 m/s), (7500 m, 11 m/s)])) wind.at(1800 m) // its direction times its speed at that height: 7 m/s east wind.direction // east, for the effects that lean on it
The wind is a record of the vocabulary: a ground direction and a curve of speed by height. wind.at(h) is the velocity at height h, the direction times the speed there. It is a fixed field, the same in every weather. It carries the clouds. The effects near the ground do not read its speeds: each states its own push: from its own tuning, as the table shows. Whether the wind may change while the world runs, and whether those pushes should follow its speeds instead, is open.
| Ground effect | Its own wind |
|---|---|
| Smoke | a push of 0.05 m/s each second, which its drag settles to 0.5 m/s downwind (Effects) |
| Rain | a push of 1.2 m/s each second, no drag |
| Snow | a push of 1.44 m/s each second against a drag of 1.2, which settles to about 1.2 m/s downwind |
| Grass and flowers | the groundcover's own: gusts traveling at 5 m/s, bending things by 0.3 plus half the rain's strength, times each kind's wind response (lawn 0.8, meadow 1, marram 1.3, ferns 0.5, flowers 0.6, moss, needles, cones and twigs 0: Generators: the cover types) |
| The wind's sound | its own strength, made from the weather (Sounds) |
What it means
Placed by direction
Sun, moons and stars are separate parts placed by a direction that may change over time. Walking never moves them, and many may be up at once (two suns light the scene together). The direction is a formula of the world's clock, date and latitude, so every machine works it out alike and nothing crosses the network. The analyticdaylight package gives the sun's direction from latitude, the day of the year and the solar hour (sunDirection in a world, sunDirectionAt in the package), and the moon always up at night, opposite the sun and 30 degrees round (a deliberate cheat); its hour leaves out the equation of time and longitude. The stars are different: they turn about the pole and the camera is always at their center, so they are placed round the camera, with the world's rotation (at: around(camera, rotation: world), Frames).
Round the camera
Far backdrops (sky, stars, a skyline) follow the camera's position only. Local fields (rain, snow, dust, insects, near grass, the ocean's surface) follow the eye too. Today's rain and snow are carried with it: they fill a disc above the camera, so a walk drags the field of drops along rather than passing through it. These parts exist once per camera, and what they show is looked up where the camera is. Looking through a far-seeing stone or a portal, the rain is round the projected eye and is that land's rain. They are not placed parts: physics and the network ignore them, streaming and culling skip them, and the renderer resolves them per camera.
No background color
What lies behind everything is whatever the designer draws there: a gradient by the atmosphere's curvature, a skybox, a dome. In a ring world, or inside a cylinder like Rama, nothing is left to fill. A plain color for pixels nothing covers is the engine's default, not content. Likewise the sky's fill light (the ambient) is an approximation of light from what is not drawn; with the surroundings drawn, it comes from them.
Weather is world state
The weather is a value the world keeps and changes, the same for every visitor, read by formulas (clouds, rain, snow, the sun's strength, the exposure, the wind's sound) and by the sky dome (the fog, the ambient, the sky's gray and dim). Moved by a pure animator, it is the cheapest kind of shared value: between changes it is a formula every machine computes alike from a start, a target, a start moment and a length; only the start of a change crosses the network, and a late joiner is told the change in progress. A change asked for halfway through a fade starts from the blend on screen, so nothing jumps.
Weather placed in the world
part storm1: storm(radius: 600 m, strength: 1), at: (...) // (to be built) derive(storm1.place, ... + wind.at(0 m) * (now(sim) - formed)) derive(rainNear.strength, rainAt(storms, camera.place))
Today the rain is the weather's: weather.rain, the same everywhere, as the example above has it. The design that would replace it is a storm, a placed part that moves with the wind; far off it is drawn as a curtain (the sheet of rain crossing the field toward you), and the near drops round the camera ask the storms how hard it is raining at each point with rainAt, so the first few drops come at its edge. Neither the storm nor rainAt is built; the examples on this page use the weather's rain.
Falling weather and roofs
Today's rain and snow fall through roofs: nothing above stops them, and they are not blocked by a cabin, a porch or a tree. This is the default of falling. The design that would stop them is blockedByRoofs: true (to be built): rain and snow are then obstructed by whatever is above, found by the engine as a shadow is, with the "light" being the sky along the fall's direction (slanted in wind), so a cabin, a porch, a bridge or a tree needs no content. Designer-marked indoor areas remain for what shape cannot say. Sound's shelter would read the same answer (Sounds).
Fidelity is the designer's. Rain stopped by roofs is one designer's choice: another may want X-rays seen passing through roofs, or rain indoors on purpose. What falling things are blocked by is a setting of the falling effect, not a fixed rule.
The wind
The wind is one value for the world, a field, wind(direction: east, speeds: curve(...)): for example east, 0.5 m/s at the ground, 7 m/s at 1800 m, 11 m/s at 7500 m, the same in every weather. The clouds read it as wind.at(h); smoke, snow, rain, the grass and the wind's sound each state their own push or strength (above). A wind whose strength follows the weather, stated once and read by all, is the design that would replace those; whether its value may change (a mutable field) is open.
Regions that override
In a labyrinth underground, for example, the weather is not drawn at all: no sun, sky, fog, rain or birds; a dim warm ambient; a fixed exposure (EV 6); a camera that sees 120 m; a drawing layer of its own. The region states its settings once and the engines that care read them.
Under the hypothesis of scenes, the labyrinth is not a region of the world but a scene of its own, with its own sky (none), light and exposure, reached through a portal (Worlds, scenes and regions). "Region" on that page means something else: a part of one scene that wakes when it might be seen. A zone inside one scene with settings of its own (a sheltered porch, deep water) is an area with settings, under the proposal on Physics: areas. A part is drawn only while its parent is, so a flock of birds that is a child of a sky is gone wherever that sky is not (Parts).
The sky as drawn
The sky is one full-screen draw of a shader fed by analyticdaylight. preetham(turbidity:) is the package's whole day sky, not Preetham's formula alone: Preetham's clear-sky model (a formula for the sky's color by direction from the sun's position and the air's haze) down to 6 degrees above the horizon, blending into a twilight patch (a glow round the sun's side and a pink band opposite) from there into the night, and a night floor, a deep blue that is never black. Over it are drawn the sun's disc, the moon's disc with its phase, and stars turning about the pole, then the clouds, then the weather's gray and dim. Stars and moon are set to read the same however the exposure is. The image-based lighting (light from the sky onto surfaces) uses an even sky that is never drawn.
The dome's weather: option is how the engine knows the weather. From it the dome takes the gray (the color drained toward a cool gray, and the stars and the moon hidden as the sky grays), the dim, the discs (the sun's disc shows by the weather's sun), the stars, a haze over the horizon, the fog and the ambient. The haze is the fog's thickness over its reach, 1 - exp(-fog * fogReach), mixed over the sky near the horizon (by the direction's rise), in the color of the sky just above the horizon; the same color is the fog's. The fog is 48 see-through sheets facing the camera from 8 m to its fogReach, 2.4 km (fogReach: 2400 m), plus the renderer's froxel fog with fogAnisotropy: 0.3 (how much it scatters light forward), made only where the graphics are good enough, driven by the weather's fog number. The ambient light is the sky's average radiance (taken over the hemisphere with a cosine weight), plus a little light thrown back from the ground and a night floor, times the weather's ambient and, channel by channel, its tint. The render package's own sky is an analytic dusk standing in for a physical atmosphere, without moon or stars.
Rain and snow are vfx emitters aimed at the viewer's eye once a frame: rain from a disc 9 m in radius 6 m above the eye, falling at 8 m/s, 2600 drops a second at full strength; snow from a disc of 10 m, about 1 m/s, 260 flakes a second (the options). Each drop is a formula of its age, so nothing is simulated or stored, and each visitor's rain is different drops.
How it connects
- Lights: the sun's light, the moon by night, exposure.
- Frames: by direction and round the camera.
- Cameras and views: per-camera parts, the far-seeing stone.
- Effects: the particles rain and snow are drawn with.
- Sounds: the wind's sound and shelter.
- Animators: the crossfade.
- Distribution: once per camera, outside client, server and both.
Open questions
- Things drawn only round each viewer's eye: how content declares a per-camera part (
around: eye), outside client, server and both. - Value types with presets: how a record like the weather state and its named values are declared; a record's declaration is written (above), but a vocabulary type with its own presets is not.
- One fact, two statements: dawn and dusk follow from date and latitude; which settings are derived, so a world cannot state ones that disagree. A proposal answers it for dusk and dawn (the sky answers them); which other settings are derived remains open.
- Where a change of weather comes from: a schedule, a random draw per run, a handler; each is expressible.
- Shelter and storms: rain and snow blocked by shape as a shadow (
blockedByRoofs), and a storm placed in the world withrainAt, are to be built; plus designer-marked areas: how the areas are stated, and how one setting on a falling effect says what blocks it. - A wind field: fixed or mutable; one wind read by the ground effects' pushes, the sound and the waves, which each state their own numbers today.
- Who may change the clock's rate: pausing stops everyone's day in a shared world.
- Recovery and settings by region: a region's overrides stated once, read by every engine. Under the hypothesis of scenes, settings belong to a scene (Worlds), and a zone's settings to an area (Physics); how an area's settings are written is open.
- A scene's own sky and clock: whether each scene has its own day, sun and weather, or shares the world's (Worlds, scenes and regions).