Chapter 26
The setting sun
The sun is the one thing in the sky that moves. As it sinks, the sky darkens, the horizon warms, and everything the sky lights dims with it. All of that is derived from the clock alone. This chapter explains the sunset in three jobs: the sun goes down, the sky darkens, and the horizon warms.
The sun goes down §
This picture does only the first job, so its sky stays blue. For each frame, the picture calls SunPath.height_at, reads out the sun's height, and marks it on the chart's gold line:
## How high the sun stands above the horizon at `clock`; below it once
## negative.
height_at : Clock -> FarHeight
height_at = |clock| sun_direction_at(clock).height
SunPath.sun_direction_at works out the sun's direction at a clock, a bearing and a height, with the ride's one piece of spherical trigonometry. The sky turns about its pole as the earth does, at a latitude of 45° on the winter solstice, so the sun sinks along an arc into the southwest. The sky's part of the sunset needs only the height. Where the sun lands on the screen needs its bearing too, which comes next.
In view, or out of view §
The sun stands in one direction, and the rider may be facing another. Two tags say whether it's in view:
## The sun in a frame: in view at its place, or out of view, too far to
## the side of where the rider faces.
Sighting : [InView(Sun.OnScreen), OutOfView]
For an OutOfView sun, Sun.sun_shapes returns an empty list, and a match on a Sighting handles that tag too, since a match covers every tag. Here's the function that returns the Sighting:
## The sun in a frame, for a camera facing `heading` through a lens of
## `focal_length`. It is in view while it is in front of the camera and its glow
## reaches the screen.
sun_in_view : { heading : Angle.Bearing, focal_length : Pixels, .. }, Clock -> Sun.Sighting
sun_in_view = |camera, clock| {
direction = SunPath.sun_direction_at(clock)
far_point = Lens.far_point({
turn: direction.bearing.turn_from(camera.heading),
height: direction.height,
focal_length: camera.focal_length,
})
match far_point {
Ok(point) => {
sun = {
center: point,
focal_length: camera.focal_length,
warmth: Sky.horizon_warmth_at(clock),
}
if is_glow_on_screen(sun) {
InView(sun)
} else {
OutOfView
}
}
Err(BehindCamera) => OutOfView
}
}
Lens.far_point says where something infinitely far off lands on the screen, and refuses with BehindCamera when it lies behind the camera. A sun that lands somewhere is still out of view when its glow stays wholly off the screen. At step 1,529, the frame the first half held still, the sun stands behind the rider, so sun_in_view returns OutOfView there. The pictures in this chapter always face the sun, so it's always in view in them.
The sun in view is a glow and a disc:
## The sun's shapes in a frame: while it is in view, its glow over the
## rectangle above the horizon, adding its light to the sky, and its disc
## over that, and nothing while it isn't.
sun_shapes : Sun.Sighting -> List(Shapes.Shape)
sun_shapes = |sun_sighting|
match sun_sighting {
InView(sun) => {
look = Sun.look(sun)
Shapes.added_light_shapes([Screen.above_horizon_rect(look.glow)]).append(
Disc({
center: sun.center,
radius: Sun.disc_radius_on_screen(sun),
fill: look.disc,
}),
)
}
OutOfView => []
}
The glow adds its light to the sky, over the rectangle above the horizon, and the disc comes after the glow in paint order. The grass comes after the backdrop's sun and covers whatever of the disc has sunk below the horizon, so the sun sets behind the land by paint order alone.
The sky darkens §
This picture adds one call, Sky.darkness_at, whose answer is the blue line, and its grass dims too. The “Functions” chapter took that function apart: it asks for the sun's height and picks one of two shapes, on either side of the moment the sun has fully set:
darkness_while_up : FarHeight -> Fraction
darkness_after_set : FarHeight -> Fraction
While the sun is up, the darkness grows with the square of how far the sun has fallen since the ride began, and reaches darkness_at_set, 0.6, as it fully sets. After that, the darkness climbs in a straight line to full night as the sun sinks a further night_depth. The two shapes meet as the sun fully sets, and you can see the join in the blue line. Both helpers take only a height, so the darkness depends on the sun's height alone.
The sky's top color blends from day to night by that darkness, as the “The sky and the grass” chapter showed, and everything the sky lights, the grass and the rock and the trees, dims through Sky.color_at_darkness by the same Fraction.
The horizon warms §
This picture adds Sky.horizon_warmth_at, the red line, and the same warmth reddens the sun's own glow and disc. The horizon is warmest when the sun's height is zero, on the horizon itself. from_horizon is how far the sun is from it, above or below, and abs makes the two sides match, which gives the red line its tent. ratio_to divides that by warmth_reach, 110, Fraction.clamp holds the ratio at 1 at most, and complement turns it around, so a height of zero gives full warmth, and 110 or more gives a warmth of zero.
Going backward §
Play any of those pictures backward, and the sun rises, with the same code. Every function here takes a clock and returns an answer, so the clock can run either way. The sun, the sky and the beacons each return their answer for any step, straight from the clock.
The rider and the truck are another kind of thing: the ride carries them, and moves them one step at a time. The next chapters explain them, starting with the rider.