# Drawing Paths

Doodle provides another type of Image that makes particular use of sequences. Paths represent arbitrary shapes created using sequences of pen movements:

val image = OpenPath(List(
MoveTo(Vec(0, 0).toPoint),
LineTo(Vec(100, 0).toPoint),
LineTo(Vec(50, 100).toPoint),
BezierCurveTo(Vec(50, 0).toPoint, Vec(0, 100).toPoint, Vec(0, 0).toPoint)
))
// image: Path = // ...

image.draw Pen movements come in three varieties:

• MoveTo(point)---move the pen to point without drawing a line;
• LineTo(point)---move the pen to point drawing a straight line;
• BezierCurveTo(cp1, cp2, point)---move the pen to point drawing a bezier curve---cp1 and cp2 are "control points" determining the shape of the curve.

The arguments in each case are objects of type Vec, which are 2D vectors representing x,y points (the name Vector is already taken by the scala.collection.Vector class). There are various ways we can create and transform Vecs:

Code Result Description Example -------------------------- --------- ------------------------------- ----------------------------------- Vec(num, num) Vec Create a vector using Vec(3, 4) x,y coordinates

Vec.polar(angle, length) Vec Create a vector using Vec.polar(30.degrees, 100) polar coordinates

Vec.zero Vec A zero vector (0,0) Vec.zero

Vec.unitX Vec A unit X vector (1,0) Vec.unitX

Vec.unitY Vec A unit Y vector (0,1) Vec.unitY

vec * num Vec Multiply vec by num Vec(2, 1) * 10

vec / num Vec Divide vec by num Vec(20, 10) / 10

vec + vec Vec Add vectors Vec(2, 1) + Vec(1, 3)

vec - vec Vec Subtract vectors Vec(5, 5) - Vec(2, 1)

vec rotate angle Vec Rotate anticlockwise by angle Vec(5, 5) rotate 45.degrees

vec.x Double Get the X component of vec Vec(3, 4).x

vec.x Double Get the Y component of vec Vec(3, 4).y

vec.length Double Get the length of vecVec(3, 4).length

We can use these operations to create paths quickly by adding vectors. Notice how we start the shape with a MoveTo element (all paths implicitly start at the origin). This is a very common pattern.

val elements = (0 to 360 by 36).map { angle =>
val point = (Vec.unitX * 100) rotate angle.degrees toPoint
val element =
if(angle == 0)
MoveTo(point)
else
LineTo(point)
element
}
// elements: scala.collection.immutable.IndexedSeq[doodle.core.PathElement] = // ...

val decagon = OpenPath(elements)
// decagon: doodle.core.Path = // ...

decagon.draw ### Exercise: My God, It's Full of Stars!

Let's use this pattern to draw some stars. For the purpose of this exercise let's assume that a star is a polygon with p points. However, instead of connecting each point to its neighbours, we'll connect them to the nth point around the circumference.

For example, the diagram below shows stars with p=11 and n=1 to 5. n=1 produces a regular polygon while values of n from 2 upwards produce stars with increasingly sharp points: Write code to draw the diagram above. Start by writing a method to draw a star given p and n:

def star(p: Int, n: Int, radius: Double): Image =
???

Create the points for your star using ranges and Vec.polar: Use your choice of recursion and beside or iteration and allBeside to create the row of stars.

<div class="solution"> Here's the star method. We've renamed p and n to points and skip for clarity:

def star(sides: Int, skip: Int, radius: Double) = {
val centerAngle = 360.degrees * skip / sides

val elements = (0 to sides) map { index =>
val point = Vec.polar(centerAngle * index, radius)
if(index == 0)
MoveTo(point)
else
LineTo(point)
}

Path(elements) strokeWidth 2
}

We'll use allBeside to create the row of stars. We only need to use values of skip from 1 to sides/2 rounded down:

(allBeside((1 to 5) map { skip =>
star(sides, skip, 100)
})).draw

</div>

When you've finished your row of stars, try constructing a larger image from different values of p and n. Here's an example: <div class="solution"> To create the image above, we started by adding colours and a chunkier outline to the definition of star:

def star(sides: Int, skip: Int, radius: Double) = {
val centerAngle = 360.degrees * skip / sides

val elements = (0 to sides) map { index =>
val point = Vec.polar(centerAngle * index, radius).toPoint
if(index == 0)
MoveTo(point)
else
LineTo(point)
}

OpenPath(elements).
strokeWidth(2).
strokeColor(Color.hsl(centerAngle, 1.normalized, .25.normalized)).
fillColor(Color.hsl(centerAngle, 1.normalized, .75.normalized))
}

The updated scene then becomes:

allAbove((3 to 33 by 2) map { sides =>
allBeside((1 to sides/2) map { skip =>
star(sides, skip, 20)
})
})`

</div>