Polar Rose in Julia

Background

I’m doing exercise 4.9 of Think Julia, which asks for a function for a polar rose using Luxor’s turtle graphics.

Difficulties

  1. Work out the geometric structure of the family of polar roses. The key is to construct some auxiliary isoceles triangles and work out the angles between them. One sees that they are parametrized by two varaibles n and k.
    • n: number of petals
    • k: petal increment
    • constraint: k ≠ n ÷ 2
  2. Handle the case when gcd(n, k) > 1, i.e. more than one closed loop.
  3. The positive x direction goes to the right; the positive y direction goes down.

Attempt

  1. Use ThinkJulia.Reposition(t::Turtle, x, y) to reposition the turtle.
  2. Use turn(t::Turtle, θ) to turn t
  3. Use ThinkJulia.Orientation(t::Turtle, θ) to restore the turtle’s orientation after the move.

Code

I spend three days writing and testing this function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
if !(@isdefined polyline) || !(typeof(polyline) <: Function)
    include("polyline.jl")
    println("loaded polyline.jl")
end

"""
polar_rose(t, n, k, r)

Draws a polar rose with the given radius and step:

    t: turtle
    n: number of petals
    k: petal increment (k < n/2)
    r: radius
"""
function polar_rose(t, n, k, r)
    t0_x, t0_y, t0_θ = t.xpos, t.ypos, t.orientation  # store orig pos & orient

    if k == n / 2  # input checking
        error("k == n ÷ 2 impossible !")
        return
    else
        reduced_k = k > n / 2 ? n - k : k
    end

    step_angle_radian, step_angle_deg = 2 * π * reduced_k / n, 360 * reduced_k / n
    arc_radius = r / (2 * cos(step_angle_radian / 2))
    arc_angle = 360 - 2 * step_angle_deg

    # Handles the case when gcd(reduced_k,n) > 1
    num_loop = gcd(reduced_k,n)
    num_arc = n ÷ num_loop
    angular_sep_radian, angular_sep_deg = 2 * π / n, 360 / n
    # At (0,0), circle(t, r) always draw circle centered at (0,r) with r < 0
    center_x = t0_x + r * cos(-(π - step_angle_radian) / 2)
    center_y = t0_y + r * sin(-(π - step_angle_radian) / 2)

    for j in 1:num_loop
        t_x = t0_x+center_x+r*cos(angular_sep_radian*(j-1)+(π+step_angle_radian)/2)
        t_y = t0_y+center_y+r*sin(angular_sep_radian*(j-1)+(π+step_angle_radian)/2)
        ThinkJulia.Reposition(t, t_x, t_y)
        if j > 1
            turn(t, angular_sep_deg * (j - 1))
        end

        for i in 1:num_arc
            arc(t, arc_radius, arc_angle)
            turn(t, -step_angle_deg)
        end
    end

    ThinkJulia.Reposition(t, t0_x, t0_y)  # restore orig pos
    ThinkJulia.Orientation(t, t0_θ)  # restore orig orient
end

To test this function, one can issue a few lines.

include("polar_rose.jl"); 🐢 = Turtle();
@svg begin
    polar_rose(🐢, 8, 3, 100)
end

Solution

The book’s solution is much simpler than mine. I’ve overthought this problem by thinking about the “petal increment”, concentric petal curves and closed loops. The author simply draws one single petal with an auxiliary function petal (t::Turtle, r, angle), which is a simple for loop.

function petal(t, r, angle)
  for i in 1:2
    arc(t, r, angle)
    turn(t, angle-180)
  end
end

The logic behind this loop is that arc(t::Turtle, r, angle) turns t’s orientation by -angle degrees due to the line turn(t, -angle) in the function polyline(t::Turtle, n, len, angle) called by arc(t, r, angle).

function polyline(t, n, len, angle)
    for i in 1:n
        forward(t, len)
        turn(t, -angle)
    end
end

N.B.: Since the y-axis is inverted in Luxor, so as the sign of an angle.


No comment

Your email address will not be published. Required fields are marked *.