# Parametric Curves

Right now we only know how to create basic shapes like circles and rectangles. We'll need more control to create the flower shapes that are our goal. We're going to use a tool from mathematics known as a parametric equation or parametric curve to do so.

A parametric equation is a function from some input (the parameter in "parametric") to a point, a location in space. The input tells us how far along the curve we are. For example, a parametric equation for a circle might have as its input an angle and it would give us the point on the circle at that angle. In Scala we could write

``````def parametricCircle(angle: Angle): Point =
???``````

If we choose lots of different values for the input, and then draw a shape at each point we get back from the parametric equation, we can suggest the shape of the curve.

In Figure hof:parametric-circles we give an example of drawing small circles at the points generated by the parametric equation for a circle. Going from left to right we draw points every 90, 45, and 22.5 degrees. You can see how the outline of the shape, the large circle, becomes clearer as we draw more points. To create parametric curves we need to learn 1) how to represent points in Doodle, 2) how to position an image at a particular point in space, and 3) revise a bit of geometry you might not have touched since high school. Let's look at each item in turn.

## Points

In Doodle we have a `Point` type to represent a position in two dimensions. We have two equivalent representations in terms of:

• x and y coordinates, called a cartesian representation; and
• in terms of an angle and distance (the radius) at that angle from the origin, called a polar representation.

This difference is shown in Figure hof:representation. We can create points in the cartesian representation using `Point(Double, Double)` where the two parameters are the x and y coordinates, and in the polar representation using `Point(Double, Angle)` where we specify the radius and the angle. The table below shows the main methods on `Point`.

+----------------------------+---------+----------------------------+---------------------------+ | Constructor |Type | Description | Example | +============================+=========+============================+===========================+ |`Point(Double, Double)` | `Point` | Constructs a `Point` using | `Point(1.0, 1.0)` | | | | the cartesian | | | | | representation. | | +----------------------------+---------+----------------------------+---------------------------+ |`Point(Double, Angle)` | `Point` | Constructs a `Point` using | `Point(1.0, 90.degrees)` | | | | the polar representation. | | +----------------------------+---------+----------------------------+---------------------------+ |`Point.zero` | `Point` | Constructs a `Point` at the| `Point.zero` | | | | origin (x and y are zero) | | +----------------------------+---------+----------------------------+---------------------------+ |`Point.x` | `Double`| Gets the x coordinate of | `Point.zero.x` | | | | the `Point`. | | +----------------------------+---------+----------------------------+---------------------------+ |`Point.y` | `Double`| Gets the y coordinate of | `Point.zero.y` | | | | the `Point`. | | +----------------------------+---------+----------------------------+---------------------------+ |`Point.r` | `Double`| Gets the radius of the | `Point.zero.r` | | | | `Point`. | | +----------------------------+---------+----------------------------+---------------------------+ |`Point.angle` | `Angle` | Gets the angle of the | `Point.zero.angle` | | | | `Point`. | | +----------------------------+---------+----------------------------+---------------------------+

## Flexible Layout

Can we position an `Image` at a point? So far we only know how to layout images with `on`, `beside`, and `above`. We need an additional tool, the `at` method, to achieve more flexible layout. Here's an example using `at` that draws a dot at the corners of a square.

``````val dot = Image.circle(5).strokeWidth(3).strokeColor(Color.crimson)
val squareDots =
dot.at(0, 0)
.on(dot.at(0, 100))
.on(dot.at(100, 100))
.on(dot.at(100, 0))``````

This produces the image shown in Figure hof:square-dots. To understand how `at` layout works, and why we have to place the dots `on` each other, we need to know a bit more about how Doodle does layout.

Every `Image` in Doodle has a point called its origin, and a bounding box which determines the limits of the image. By convention the origin is in the center of the bounding box but this is not required. We can see the origin and bounding box of an `Image` by calling the `debug` method. In Figure hof:debug we show the output of the code

``````val c = Image.circle(40)
val c1 = c.beside(c.at(10, 10)).beside(c.at(10, -10)).debug
val c2 = c.debug.beside(c.at(10, 10).debug).beside(c.at(10, -10).debug)
val c3 = c.debug.beside(c.debug.at(10, 10)).beside(c.debug.at(10, -10))
c1.above(c2).above(c3)``````

This shows how the origin and bounding box change as we combines `Images`. When we layout `Images` using `above`, `beside`, or `on` it is the bounding boxes and origins that determine how the individual components are positioned relative to one another. For `on` the rule is that the origins are placed on top of one another. For `beside` the rule is that origins are horizontally aligned and placed so that the bounding boxes just touch. The origin of the compound image is placed equidistant from the left and right edges of the compound bounding box on the horizontal line that connects the origins of the component images. The rule for `above` is the same as `beside`, but we use vertical alignment instead of horizontal alignment.

Using `at` we can move an `Image` relative to its origin. In the examples we're using here we want all the elements to share the same origin, so we use `on` to combine `Images` that we have moved using `at`.

There are four ways we can call `at`:

• by passing the x- and y-offset, as in `dot.at(100, 100)`;
• by passing the radius and angle, as in `dot.at(100, 90.degrees)`;
• by passing a `Point`, as in `dot.at(Point(100, 100))`; or
• by passing a `Vec` (a vector) giving the offset, as in `dot.at(Vec(100, 100))`.

We can convert a `Point` to a `Vec` using the `toVec` method.

``````Point.cartesian(1.0, 1.0).toVec
// res1: Vec = Vec(x = 1.0, y = 1.0)``````

## Geometry

The final building block is the geometry to position points. If a point is positioned at a distance `r` from the origin at an angle `a`, the x- and y-coordinates are `(a.cos) * r` and `(a.sin) * r` respectively. Alternatively we can just use polar form! For example, here's how we would position a point at a distance of 1 and an angle of 45 degrees.

``````val polar = Point(1.0, 45.degrees)
// polar: Point = Polar(r = 1.0, angle = Angle(0.7853981633974483))
val cartesian = Point((45.degrees.cos) * 1.0, (45.degrees.sin) * 1.0)
// cartesian: Point = Cartesian(x = 0.7071067811865476, y = 0.7071067811865475)

// They are the same
polar.toCartesian == cartesian
// res2: Boolean = true
cartesian.toPolar == polar
// res3: Boolean = true``````

## Putting It All Together

We can put this all together to create a parametric circle. In cartesian coordinates the code for a parametric circle with radius 200 is

``````def parametricCircle(angle: Angle): Point =
Point.cartesian(angle.cos * 200, angle.sin * 200)``````

In polar form it is simply

``````def parametricCircle(angle: Angle): Point =
Point.polar(200, angle)``````

Now we could sample a number of points evenly spaced around the circle. To create an image we can draw something at each point (say, a triangle).

``````def sample(samples: Int): Image = {
// Angle.one is one complete turn. I.e. 360 degrees
val step = Angle.one / samples
val dot = Image
.triangle(10, 10)
.fillColor(Color.limeGreen)
.strokeColor(Color.lawngreen)
def loop(count: Int): Image = {
val angle = step * count
count match {
case 0 => Image.empty
case n =>
dot.at(parametricCircle(angle)).on(loop(n - 1))
}
}

loop(samples)
}``````

This is a structural recursion, which is hopefully a familiar pattern by now.

If we draw this we'll see the outline of a circle suggested by the triangles. See Figure hof:triangle-circle, which shows the result of `sample(72)`. ### Parametric Curves as First-class Functions

So far we haven't seen anything that requires we use our parametric curves as functions instead of methods (and, indeed, we have defined them as method though we know we can easily convert methods to functions.) It's time we got something useful from functions. Remember that functions are first-class values, which means: we can pass them to a method, we can return them from a method, and we can give them a name using `val`. We're going to see an example where the first property---the ability to pass them as parameters---is useful.

We've just defined a method called `sample` that samples from our parametric curve. Right now it is restricted to sampling from the method `parametricCircle`. It would make a lot of sense to reuse this method with different parametric curves, which means we need to be able to pass a parametric curve to sample from to the `sample` method. We can do this with a function parameter. Here is what the code might look like.

``````def sample(samples: Int, dot: Image, curve: Angle => Point): Image = {
val step = Angle.one / samples
def loop(count: Int): Image = {
val angle = step * count
count match {
case 0 => Image.empty
case n =>
dot.at(curve(angle)).on(loop(n - 1))
}
}

loop(samples)
}``````

In this implementation of sample I have added two new parameters, the parametric curve to sample from and the `Image` to use to draw the samples. This gives us more flexibility in the output. Now we just need to define some more parametric curves, which is what the next exercise involves.

#### Exercises {-}

We have some new tools in our toolbox. It's time to have some fun exploring what we can do with them.

##### Spirals

To create a circle we keep the radius constant as the angle increases. If, instead, the radius increases as the angle increases we'll get a spiral. (How quickly should the radius increase? It's up to you! Different choices will give you different spirals.)

Implement a function or method `parametricSpiral` that creates a spiral.

<div class="solution"> Here's a type of spiral, known as a logarithmic spiral, that has a particularly pleasing shape. `sample` it and see for yourself!

``````def parametricSpiral(angle: Angle): Point =
Point((Math.exp(angle.toTurns) - 1) * 200, angle)``````

</div>

##### Samples

Use the parametric curves we have defined so far to create something interesting. There is an example in Figure hof:psychedelic-spirals Conclusions→