Skip to content

Commit

Permalink
Start on conclusions
Browse files Browse the repository at this point in the history
  • Loading branch information
noelwelsh committed Jun 30, 2023
1 parent 70099b8 commit 009c6db
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 3 deletions.
1 change: 1 addition & 0 deletions book/src/main/scala/cycles/All.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ object All {
Fun
Interpolation
Epicycles
Final
}
111 changes: 111 additions & 0 deletions book/src/main/scala/cycles/Final.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package creativescala
package cycles

import cats.implicits._
import doodle.core._
import doodle.image._
import doodle.syntax.all._
import doodle.image.syntax.all._
import doodle.java2d._

object Final {

def drawCurve(samples: Int, stop: Angle = Angle.one)(
dot: Angle => Image
)(f: Angle => Point): Image = {
// Angle.one is one complete turn. I.e. 360 degrees
val step = stop / samples
def loop(count: Int): Image = {
val angle = step * count
count match {
case 0 => Image.empty
case n =>
dot(angle).at(f(angle)).on(loop(n - 1))
}
}

loop(samples)
}

def sampleCurve(samples: Int, stop: Angle = Angle.one)(
curve: Angle => Point
): List[Point] = {
// Angle.one is one complete turn. I.e. 360 degrees
val step = stop / samples
def loop(count: Int): List[Point] = {
count match {
case 0 => List.empty
case n =>
val angle = step * count
curve(angle) :: loop(n - 1)
}
}

loop(samples)
}

def scale(factor: Double): Point => Point =
(point: Point) => Point(point.r * factor, point.angle)

val parametricCircle: Angle => Point =
(angle: Angle) => Point(1.0, angle)

val parametricSpiral: Angle => Point =
(angle: Angle) => Point(Math.exp(angle.toTurns - 1), angle)

def lissajous(a: Int, b: Int, offset: Angle): Angle => Point =
(angle: Angle) => Point(((angle * a) + offset).sin, (angle * b).sin)

def rose(k: Int): Angle => Point =
(angle: Angle) => {
Point.cartesian((angle * k).cos * angle.cos, (angle * k).cos * angle.sin)
}

def wheel(speed: Int): Angle => Point =
angle => Point(1.0, angle * speed)

def on(curve1: Angle => Point, curve2: Angle => Point): Angle => Point =
angle => curve1(angle) + curve2(angle).toVec

val nSamples = 350

Image
.path(
ClosedPath.interpolatingSpline(
sampleCurve(nSamples)(
on(
lissajous(2, 3, 0.degrees).andThen(scale(150)),
rose(7).andThen(scale(100))
)
)
)
)
.strokeColor(Color.darkBlue)
.save("cycles/lissajous-rose")

Image
.path(
ClosedPath.interpolatingSpline(
sampleCurve(nSamples)(
on(
wheel(5).andThen(scale(50)),
lissajous(2, 3, 0.degrees).andThen(scale(150))
)
)
)
)
.strokeColor(Color.darkBlue)
.save("cycles/wheel-lissajous")

drawCurve(60)(angle =>
Image
.circle(angle.toTurns * 24 + 2)
.strokeColor(Color.blue.saturation(0.7.normalized).spin(angle * 0.2))
.strokeWidth(3.0)
)(
on(
wheel(5).andThen(scale(50)),
lissajous(2, 3, 0.degrees).andThen(scale(150))
)
).save("cycles/wheel-lissajous-circles")
}
8 changes: 6 additions & 2 deletions book/src/pages/cycles/conclusions.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Conclusions

Function composition.
This chapter has focused on function composition.
We've used `Angle => Point` functions to represent parametric curves,
and we have seen that we can build many useful abstractions around them by using function composition.

Functions are not the only things that compose. Every time we've been used `beside`, or `on`, for example, we've been composing images.
Functions are not the only things that compose.
Every time we've used the `beside`, or `above` methods on `Image`, for example, we've been composing images.
This notion of composition is one of the key ideas in functional programming, and we'll continue to see it throughout the book.

closure

Expand Down
21 changes: 21 additions & 0 deletions book/src/pages/cycles/culmination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Bringing it Together

We're now going to bring together everything we've used in this chapter to create our final image and close out this part of the book.

We've built a whole raft of methods that all work with a common abstraction of `Angle => Point` functions.
For example, we can use `on`, which we developed to compose wheels, with any parametric curve.
In the example below we have a rose curve rotating around (`on`) a Lissajous curve, which gives an interesting curve that would be difficult to come up with by hand.

@:figure{ img = "./lissajous-rose.svg", key = "#fig:cycles:lissajous-rose", caption = "A rose curve rotating around a Lissajous curve." }

Placing a wheel on a Lissajous curve also gives an interesting result.

@:figure{ img = "./wheel-lissajous.svg", key = "#fig:cycles:wheel-lissajous", caption = "A rose curve rotating around a Lissajous curve." }

Remember we're not restricted to interpolating splines.
Here's the same curve, but I'm drawing circles at selected points (where the size and color of the circle depends on the angle).

@:figure{ img = "./wheel-lissajous-circles.svg", key = "#fig:cycles:wheel-lissajous-circles", caption = "A rose curve rotating around a Lissajous curve." }

Now it's over to you.
Give yourself a good chunk of time and, using the tools we've made, create an image that you're proud of to mark your progress so far.
2 changes: 1 addition & 1 deletion book/src/pages/cycles/directory.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ laika.navigationOrder = [
fun.md
interpolation.md
epicycles.md
sketching.md
culmination.md
conclusions.md
]

0 comments on commit 009c6db

Please sign in to comment.