Chapter 9
Shapes
A far tree in the ride is a trunk under eight triangles. So before we build a tree, we build its two shapes: a triangle and a rectangle.
In Safari Ride, a shape is a value that describes something to paint. The first four tags of the Shapes module's Shape type are the kinds of region the platform paints, each a region of the screen with a fill:
# ...
Shape : [
## Three corners, which is every triangle and nothing else.
Triangle(
{
first : Point,
second : Point,
third : Point,
fill : Brush.Fill,
},
),
## Four corners in order around the edge. `Shapes.rectangle` makes an
## upright one.
Quad(
{
first : Point,
second : Point,
third : Point,
fourth : Point,
fill : Brush.Fill,
},
),
## A circle, `radius` out from `center`.
Disc(
{
center : Point,
radius : Pixels,
fill : Brush.Fill,
},
),
## An outline of any other number of corners, in order around its edge: a
## traced still, a mountain, the road.
Polygon({ corners : List(Point), fill : Brush.Fill }),
# ...
Each tag carries a record, and every record ends with a fill, the paint for the inside. The ride produces a great many triangles, plenty of four-cornered things, a few round lights, and outlines traced from emoji and mountains, so those are its four regions. More tags follow them in the type, for other jobs, such as a line of text, and the “The platform” chapter takes two of them.
A triangle §
Here's a triangle:
And here's the code that makes it:
## A green triangle.
green_triangle : Shapes.Shape
green_triangle = Triangle({
first: Point.at(180.0, 262.0),
second: Point.at(420.0, 262.0),
third: Point.at(300.0, 42.0),
fill: Brush.flat(RgbColor.of(0x2e8b57)),
})
The value is a Triangle tag with its record. The record names three corners, first, second and third, so every triangle has exactly three, by construction. A corner is a Point, a record with an x and a y. Point.at makes one from two plain numbers, measured from the top left of the picture, with y growing downward. The picture is 640 pixels wide and 324 tall.
Point.at turns each of those numbers into a Pixels, an opaque type around an F64, like Distance. The ride works in metres on the road and in pixels on the screen, and two types keep the two apart, so each number's type names its unit.
The fill is green. RgbColor.of makes a color from 0x2e8b57. The 0x marks a number in hex: the six digits a web page writes after its #. Brush.flat makes a fill of that one color.
The platform paints the triangle. The picture is a small Roc app of its own, and it hands the platform a list of two shapes: the background, a rectangle that fills the picture, and then the triangle. The platform paints the list from first to last, so the triangle lands on the background.
A rectangle §
Next, a rectangle:
## A brown rectangle.
brown_rectangle : Shapes.Shape
brown_rectangle = Shapes.rectangle({
top_left: Point.at(240.0, 82.0),
width: Pixels.of(120.0),
height: Pixels.of(160.0),
fill: Brush.flat(RgbColor.of(0x8b5a2b)),
})
The rectangle's code has the triangle's structure:
| The triangle | The rectangle | |
|---|---|---|
| Its type | Shapes. |
Shapes. |
| What builds it | the Triangle tag |
the Shapes. function, which returns a Quad |
| Where it is | three corners, each from Point. |
a top left corner from Point., and a width and a height from Pixels. |
| Its fill | Brush., green |
Brush., brown |
Here's the function:
## An upright box, `width` across and `height` down from `top_left`, as the
## quad it is: clockwise on the screen from its top left corner.
rectangle : {
top_left : Point,
width : Pixels,
height : Pixels,
fill : Brush.Fill,
} -> Shapes.Shape
rectangle = |{ top_left, width, height, fill }| {
right = top_left.x.plus(width)
bottom = top_left.y.plus(height)
Quad({
first: top_left,
second: { x: right, y: top_left.y },
third: { x: right, y: bottom },
fourth: { x: top_left.x, y: bottom },
fill,
})
}
|{ top_left, width, height, fill }| takes the record apart, naming each of its fields. The body works out where the right and bottom edges are, with plus, which adds two Pixels. Then it gives the quad its four corners, clockwise from the top left. Each side's two corners share an x or a y, so the sides are always square to the screen.
A far tree needs just those two shapes. Next, we put them together, with functions of our own.