Fractal Tree

Code

Tree module · lib/phx_demo/examples/tree.ex
defmodule PhxDemo.Examples.Tree do
  def render do
    random_seed()
    width = 600
    height = 500

    canvas =
      Easel.new(width, height)
      |> Easel.set_fill_style("#87CEEB")
      |> Easel.fill_rect(0, 0, width, height)
      |> Easel.set_fill_style("#3d5a1e")
      |> Easel.fill_rect(0, height - 40, width, 40)

    draw_tree(canvas, width / 2, height - 40, 100, -:math.pi() / 2, 0, 10)
    |> Easel.render()
  end

  defp random_seed do
    a = :erlang.unique_integer([:positive])
    b = :erlang.phash2({System.system_time(), self()})
    c = :erlang.phash2({System.monotonic_time(), make_ref()})
    :rand.seed(:exsss, {a, b, c})
  end

  defp draw_tree(canvas, _x, _y, _length, _angle, depth, max) when depth >= max, do: canvas

  defp draw_tree(canvas, x, y, length, angle, depth, max_depth) do
    end_x = x + length * :math.cos(angle)
    end_y = y + length * :math.sin(angle)

    t = depth / max_depth
    r = round(80 * (1 - t) + 34 * t)
    g = round(50 * (1 - t) + 139 * t)
    b = round(20 * (1 - t) + 34 * t)
    line_w = max(1, (max_depth - depth) * 1.5)

    canvas =
      canvas
      |> Easel.begin_path()
      |> Easel.move_to(x, y)
      |> Easel.line_to(end_x, end_y)
      |> Easel.set_stroke_style("rgb(#{r}, #{g}, #{b})")
      |> Easel.set_line_width(line_w)
      |> Easel.set_line_cap("round")
      |> Easel.stroke()

    canvas =
      if depth >= max_depth - 2 do
        size = 3 + :rand.uniform(4)

        canvas
        |> Easel.begin_path()
        |> Easel.arc(end_x, end_y, size, 0, :math.pi() * 2)
        |> Easel.set_fill_style("rgba(34, #{100 + :rand.uniform(100)}, 34, 0.6)")
        |> Easel.fill()
      else
        canvas
      end

    new_length = length * (0.65 + :rand.uniform() * 0.1)
    spread = 0.4 + :rand.uniform() * 0.2

    canvas
    |> draw_tree(end_x, end_y, new_length, angle - spread, depth + 1, max_depth)
    |> draw_tree(end_x, end_y, new_length, angle + spread, depth + 1, max_depth)
  end
end