Chapter 11
The pipeline
For each frame, every thing beside the road takes the same trip to the screen. In the “Lists and map: a far tree” chapter, TreeShapes.tree_sprite takes a setting and returns a tree's sprite. This chapter follows the trip around that function: where the setting comes from, and where the sprite goes. A tree, a cow, a radio tower, the cat, the bird and the truck each start from a place on the road, and end as a rendering, a sprite with a depth, in the frame's one list. The Pipeline module holds the functions that sight and render, so we call the trip the pipeline.
Here's the trip:
Each sort of thing has a sighting module, such as TreeSighting, and some serve several kinds: BillboardSighting sights every billboard. A sighting module gathers its placed things on the road ahead, and hands each one to a Pipeline function that sights it, as the “Errors as tag unions” chapter shows for TreeSighting.
Placed §
The trip starts from where a thing stands on the road:
## An object on one segment of the road, `lift` off the ground, and what
## kind of object it is. A tree is placed once for the whole ride, a cow
## from its herd whenever something asks for it, and a cat, a tower or the
## truck anew for each frame.
## `Pipeline.sighting_of` turns it into a `Pipeline.Sighted` for a frame.
Placed(kind) : {
road_point : Geom.RoadPoint,
lift : Distance,
kind : kind,
}
kind says what the thing is. Its type is the type variable kind, so a placed tree is a Geom.Placed(Conifer), and a placed cow is a Geom.Placed(Billboard). lift says how far off the ground it stands, and road_point is its place on the road:
## A point on one segment of the road.
RoadPoint : { along : Along, across : Across }
along is how far the place lies along its segment of road, and across is how far it lies from the middle of the road, growing to the right. Each is an opaque type around a number of metres, as Distance is:
Along :: F64.{
metres : F64 -> Along
metres = |metres| Along.(metres)
# ...
So the compiler rejects a call that passes an across where an along belongs.
Sighted §
The screen shows what the camera sees, and the camera is the rider's eye: it sits 1.2 metres above the road and looks where the rider looks. So for each frame, the pipeline sights every placed thing in play, and works out where it stands from the camera:
## An object in the rider's view, and what kind of object it is, for one
## frame.
Sighted(kind) : { spot : Spot, kind : kind }
A sighting keeps the thing's kind and swaps its place on the road for a Spot, a place seen from the camera:
Spot :: {
lift : Distance,
foot : RiderView.RiderPointAhead,
road_heading : Angle.Turn,
}.{
# ...
lift is still how far off the ground the thing stands, and road_heading is which way the road runs there. foot is where the thing stands in the camera's view, and its type starts from a plain record of two distances:
## A point on the ground in the rider's view. Both parts are signed: a
## negative `right` is to the rider's left, and a negative `forward` is
## behind the rider.
RiderPoint : { right : Distance, forward : Distance }
right and forward count metres from the camera, and a negative forward is behind it. A RiderView.RiderPointAhead is the same record behind a ::, and only RiderView.rider_point_ahead builds one, from a point in front of the camera. So a spot's foot is always in front of the camera, by construction. From the foot, the ride finds where the thing lands on the screen and how big it looks there, by one rule of perspective: a thing twice as far off looks half as big, and lands twice as close to the middle of the screen. The “The math layer” chapter shows the math. This spot is the one tree_sprite took from its setting in the “Lists and map: a far tree” chapter.
A sighting function can also return a refusal, such as OutOfSight, in place of a sighting. The rest of this chapter shows what happens to the sightings that are kept.
Rendered §
Pipeline.rendering_of, which the “Where clauses: asking a kind” chapter explains, renders each sighting that's kept, and returns a Rendered:
Rendered :: { depth : Distance, sprite : Sprite }.{
## The rendering of `sprite`, standing `depth` ahead of the camera.
of : Distance, Sprite -> Rendered
of = |depth, sprite| Rendered.({ depth, sprite })
# ...
A Rendered holds a depth and a sprite. The :: makes it opaque, as it does Distance. The sprite is what a kind's scenery_sprite returns from a setting, and the depth is how far ahead of the camera the thing stands. A tree's rendering and a cow's have the same type.
One list §
Every rendering has that one type, so a frame gathers them all in one list:
## The scene's renderings far to near. The order of the fields they come
## from breaks ties, so the trees go before the birds: a perched bird stands
## at its tree's own depth and is drawn over it.
scene_renderings : Scene -> List(Rendered)
scene_renderings = |scene|
Rendered.depth_sorted_renderings(
[
scene.trees,
scene.birds,
scene.towers,
scene.billboards,
scene.cats,
scene.trucks,
scene.rails,
].join(),
)
A Scene holds a list of renderings for each sort of thing, and join makes one list of them all. The guard rail's renderings come in through scene.rails, as the “Rails” chapter shows. Rendered.depth_sorted_renderings sorts the list from far to near with DepthSort.sort_by, and the platform paints it in that order, so a near thing covers a far one.
So every thing beside the road makes the same trip: placed on the road, sighted by the camera, rendered as a sprite with a depth, and sorted into the frame's one list. Its kind rides through the first two types, and the third holds just a sprite and a depth. The trip can also end early, at the sighting, and the “Errors as tag unions” chapter goes back to that box. First, the next chapter shows how rendering_of, written once, gets a sprite out of every kind.