Example Usage

# ===== Default : Default
GEM_HOME = '/home/tux/.gem/ruby/2.6.0'
require 'vecmath'
require 'forwardable'

# Here we use the JRubyArt Vec2D class, and GfxRender class (for vec.to_vertex)
# Further we use the power of ruby (metaprogramming) to make Branch enumerable
# and use Forwardable to define which enumerable methods we want to use.
visor_class :Branch do
  include Enumerable
  extend Forwardable
  def_delegators(:@children, :<<, :each, :length)
  # variance angle for growth direction per time step
  THETA = Math::PI / 6
  # max segments per branch
  MAX_LEN = 100
  # max recursion limit
  MAX_GEN = 3
  # branch chance per time step
  BRANCH_CHANCE = 0.05
  # branch angle variance
  BRANCH_THETA = Math::PI / 3
  attr_reader :position, :dir, :path, :children, :xbound, :speed, :ybound, :app

  def initialize(pos, dir, speed)
    @renderer ||= GfxRender.new(graphics)
    @position = pos
    @dir = dir
    @speed = speed
    @path = []
    @children = []
    @xbound = Boundary.new(0, width)
    @ybound = Boundary.new(0, height)
    path << pos
  end

  def run
    grow
    display
  end

  private

  # NB: use of both rotate! (changes original) rotate (returns a copy) of Vec2D
  def grow
    check_bounds(position + (dir * speed)) if path.length < MAX_LEN
    @position += (dir * speed)
    dir.rotate!(rand(-0.5..0.5) * THETA)
    path << position
    if (length < MAX_GEN) && (rand < BRANCH_CHANCE)
      branch_dir = dir.rotate(rand(-0.5..0.5) * BRANCH_THETA)
      self << Branch.new(position.copy, branch_dir, speed * 0.99)
    end
    each(&:grow)
  end

  def display
    begin_shape
    stroke(255)
    no_fill
    path.each { |vec| vec.to_vertex(@renderer) }
    end_shape
    each(&:display)
  end

  def check_bounds(pos)
    dir.x *= -1 if xbound.exclude? pos.x
    dir.y *= -1 if ybound.exclude? pos.y
  end
end

# we are looking for excluded values
Boundary = Struct.new(:lower, :upper) do
  def exclude?(val)
    !(lower...upper).cover? val
  end
end

@root = Branch.new(
    Vec2D.new(0, height / 2),
    Vec2D.new(1, 0),
    10.0
)

def draw
  background(0)
  root.run
end