By: Jeff Blackadar @jeffblackadar

References

Prusinkiewicz, Przemyslaw, and Aristid Lindenmayer. The Algorithmic Beauty of Plants. New York u.a: Springer, 1990.

Spannbauer, Adam. OOP Fractal Trees in R with R6, ggplot2, & gganimate (part 1) https://adamspannbauer.github.io/2018/10/07/oop-fractal-trees-in-r-with-r6-ggplot2-gganimate-part-1/

Thanks to Adam Spannbauer’s article noted above, R6 classes and plotting of fractals are used here. Page numbers referenced refer to Przemyslaw Prusinkiewicz and Aristid Lindenmayer’s The Algorithmic Beauty of Plants.

Scroll down to see some examples, or view the other pages. The repository for code used here is https://github.com/jeffblackadar/fractal.

library(R6)
library(ggplot2)
library(uuid)
options(stringsAsFactors = FALSE)
radian_conversion <- pi / 180

# from Adam Spannbauer OOP Fractal Trees in R with R6, ggplot2, & gganimate (part 1) https://adamspannbauer.github.io/2018/10/07/oop-fractal-trees-in-r-with-r6-ggplot2-gganimate-part-1/

line_base = R6Class(
  'line_base',
  public = list(
    start_x = NA_real_,
    start_y = NA_real_,
    end_x = NA_real_,
    end_y = NA_real_,
    type = NA_character_,
    id = NA_character_,
    line_color = NA_character_,

    
    initialize = function(x,
                          y,
                          len = 5,
                          theta = 90,
                          type = NA_character_,
                          line_color = '#000000') {
      dy = sin(theta * radian_conversion) * len
      dx = cos(theta * radian_conversion) * len
      
      self$start_x = x
      self$start_y = y
      self$end_x = x + dx
      self$end_y = y + dy
      
      self$type = type
      self$id = uuid::UUIDgenerate()
      self$line_color = line_color
    } #initialize
  ),
  # public
  
  active = list(
    df = function() {
      x = c(self$start_x, self$end_x)
      y = c(self$start_y, self$end_y)
      
      data.frame(
        x = x,
        y = y,
        type = self$type,
        id = self$id,
        line_color = self$line_color
      )
    }
  )  # active
)  # line_base



fractal_l_generate = R6Class(
  'fractal_l_generate',
  public = list(
    start_x = NA_real_,
    start_y = NA_real_,
    end_x = NA_real_,
    end_y = NA_real_,
    angle_delta = NA_real_,
    angle_current =  NA_real_,
    line_length = NA_character_,
    length_decay = NA_real_,
    min_length = NA_real_,
    fractal_lines = data.frame(),
    type = NA_character_,
    initiator = NA_character_,
    generators = list(),
    generator_number = NA_integer_,
    generation_current = NA_integer_,
    generation_max = NA_integer_,
    
    initialize = function(start_x = 0,
                          start_y = 0,
                          angle_delta = 0,
                          angle_current = 0,
                          line_length = 10,
                          length_decay = 1,
                          min_length = .25,
                          fractal_lines = data.frame(),
                          type = "",
                          initiator = "",
                          generators = c(""),
                          generator_number = 1,
                          generation_current = 0,
                          generation_max = 0) {
      self$start_x = start_x
      self$start_y = start_y
      self$angle_delta = angle_delta
      self$angle_current = angle_current
      self$line_length = line_length
      self$length_decay = length_decay
      self$min_length = min_length
      
      self$type = type
      self$initiator <- initiator
      self$generators <- generators
      self$generator_number <- generator_number
      self$generation_current <-
        generation_current
      self$generation_max <- generation_max
      
      
      fractal_instruction_line <- self$initiator
      if (self$generation_current == 0) {
        fractal_instruction_line <- self$initiator
      } else{
        fractal_instruction_line <- self$generators[self$generator_number]
      }
      
      generation_line_color <- "#000000"

      for (i in 1:nchar(fractal_instruction_line)) {
        fractal_instruction_char <- substr(fractal_instruction_line, i, i)
        
        if (fractal_instruction_char == 'F' |
            fractal_instruction_char == 'L' |
            fractal_instruction_char == 'R') {
          if (self$generation_current == self$generation_max) {
            new_line <-
              line_base$new(
                self$start_x,
                self$start_y,
                self$line_length,
                self$angle_current,
                self$type,
                generation_line_color
              )
            self$fractal_lines = rbind(self$fractal_lines, new_line$df)
            self$start_x <- new_line$end_x
            self$start_y <- new_line$end_y
          }
          else{
            #For F and L use generator_number = 1.  For R use generator_number = 2
            generator_number_temp <- 1
            if (fractal_instruction_char == 'R') {
              generator_number_temp <- 2
            }
            next_generation <-
              fractal_l_generate$new(
                start_x = self$start_x,
                start_y = self$start_y,
                angle_delta = self$angle_delta,
                angle_current = self$angle_current,
                line_length = self$line_length,
                length_decay = self$length_decay,
                min_length = self$min_length,
                type = self$type,
                initiator = self$initiator,
                generators = self$generators,
                generator_number = generator_number_temp,
                #F uses the first generator
                generation_current = self$generation_current + 1,
                generation_max = self$generation_max
              )
            
            self$fractal_lines = rbind(self$fractal_lines,
                                       next_generation$fractal_lines)
            
            self$angle_current <-
              next_generation$angle_current
            self$start_x <-
              next_generation$start_x
            self$start_y <-
              next_generation$start_y
          }
        }
        else{
          if (fractal_instruction_char == 'f') {
            if (self$generation_current == self$generation_max) {
              new_line <-
                line_base$new(
                  self$start_x,
                  self$start_y,
                  self$line_length,
                  self$angle_current,
                  self$type,
                  generation_line_color
                )
              self$start_x <- new_line$end_x
              self$start_y <- new_line$end_y
              
            }
            else{
              
              
              next_generation <-
                fractal_l_generate$new(
                  start_x = self$start_x,
                  start_y = self$start_y,
                  angle_delta = self$angle_delta,
                  angle_current = self$angle_current,
                  line_length = self$line_length,
                  length_decay = self$length_decay,
                  min_length = self$min_length,
                  type = self$type,
                  initiator = self$initiator,
                  generators = self$generators,
                  generator_number = 2,
                  #f uses the second generator
                  generation_current = self$generation_current + 1,
                  generation_max = self$generation_max
                )
              self$angle_current <-
                next_generation$angle_current
              self$start_x <-
                next_generation$start_x
              self$start_y <-
                next_generation$start_y
            }
          }
          else{
            if (fractal_instruction_char == 'T') {
              self$line_length <- self$line_length * length_decay
              if (self$line_length < self$min_length) {
                self$line_length <- self$min_length
              }
              generator_number_temp <- 1
              
              #trunk line
              new_line <-
                line_base$new(
                  self$start_x,
                  self$start_y,
                  self$line_length,
                  self$angle_current,
                  self$type,
                  generation_line_color
                )
              self$fractal_lines = rbind(self$fractal_lines, new_line$df)
              start_x_right <- new_line$end_x
              start_y_right <- new_line$end_y
              
              if (self$generation_current < self$generation_max) {
                next_generation <-
                  fractal_l_generate$new(
                    start_x = start_x_right,
                    start_y = start_y_right,
                    angle_delta = self$angle_delta,
                    angle_current = self$angle_current,
                    line_length = self$line_length,
                    length_decay = self$length_decay,
                    min_length = self$min_length,
                    type = self$type,
                    initiator = self$initiator,
                    generators = self$generators,
                    generator_number = generator_number_temp,
                    generation_current = self$generation_current + 1,
                    generation_max = self$generation_max
                  )
                
                self$fractal_lines = rbind(self$fractal_lines,
                                           next_generation$fractal_lines)
              }
            } # T
            
            else {
              if (fractal_instruction_char == 'B') {
                angle_right <- self$angle_current + self$angle_delta
                angle_left <- self$angle_current - self$angle_delta
                self$line_length = self$line_length * self$length_decay
                if (self$line_length < self$min_length) {
                  self$line_length = self$min_length
                }
                
                generator_number_temp <- 1
                #right line
                
                generation_line_color="green"
                if(self$generation_current>0 & self$generation_current<4){
                  generation_line_color="brown"
                }else{
                  if(self$generation_current>3 & self$generation_current<7){
                    generation_line_color="limegreen"
                  }else{
                    if(self$generation_current>6){
                      generation_line_color="green"
                    }else{
                      generation_line_color="pink"
                    } 
                  }
                  
                }
                generation_line_color
                new_line <-
                  line_base$new(
                    self$start_x,
                    self$start_y,
                    self$line_length,
                    angle_right,
                    self$type,
                    generation_line_color
                  )
                self$fractal_lines = rbind(self$fractal_lines, new_line$df)
                start_x_right <- new_line$end_x
                start_y_right <- new_line$end_y
                
                if (self$generation_current < self$generation_max) {
                  next_generation <-
                    fractal_l_generate$new(
                      start_x = start_x_right,
                      start_y = start_y_right,
                      angle_delta = self$angle_delta,
                      angle_current = angle_right,
                      line_length = self$line_length,
                      length_decay = self$length_decay,
                      min_length = self$min_length,
                      type = self$type,
                      initiator = self$initiator,
                      generators = self$generators,
                      generator_number = generator_number_temp,
                      generation_current = self$generation_current + 1,
                      generation_max = self$generation_max
                    )
                  
                  self$fractal_lines = rbind(self$fractal_lines,
                                             next_generation$fractal_lines)
                }
                
                #left line
                new_line <-
                  line_base$new(
                    self$start_x,
                    self$start_y,
                    self$line_length,
                    angle_left,
                    self$type,
                    generation_line_color
                  )
                self$fractal_lines = rbind(self$fractal_lines, new_line$df)
                start_x_left <- new_line$end_x
                start_y_left <- new_line$end_y
                
                if (self$generation_current < self$generation_max) {
                  next_generation <-
                    fractal_l_generate$new(
                      start_x = start_x_left,
                      start_y = start_y_left,
                      angle_delta = self$angle_delta,
                      angle_current = angle_left,
                      line_length = self$line_length,
                      length_decay = self$length_decay,
                      min_length = self$min_length,
                      type = self$type,
                      initiator = self$initiator,
                      generators = self$generators,
                      generator_number = generator_number_temp,
                      generation_current = self$generation_current + 1,
                      generation_max = self$generation_max
                    )
                  
                  self$fractal_lines = rbind(self$fractal_lines,
                                             next_generation$fractal_lines)
                }
                
              } else{
                if (fractal_instruction_char == '+') {
                  self$angle_current = self$angle_current + self$angle_delta
                }
                else{
                  if (fractal_instruction_char == '-') {
                    self$angle_current = self$angle_current - self$angle_delta
                  } else{
                    print("Unhandled instruction!")
                  } # else -
                }# else +
              } # else B
            } # else T
          } #else f
        } #else F
      } #for
    } # initialize
  )  # public
) # class


# Class fractal_l

fractal_l = R6Class(
  'fractal_l',
  public = list(
    start_x = NA_real_,
    start_y = NA_real_,
    angle_delta = NA_real_,
    angle_current =  NA_real_,
    line_length = NA_character_,
    length_decay = NA_real_,
    min_length = NA_real_,
    fractal_lines = data.frame(),
    type = NA_character_,
    initiator = NA_character_,
    generators = list(),
    generator_number = NA_integer_,
    generation_current = NA_integer_,
    generation_max = NA_integer_,
    fractal_definitions = data.frame(),
    
    initialize = function(line_length = 10,
                          angle_delta = pi / 4,
                          angle_current = 0,
                          initiator = "",
                          generators = c(""),
                          generator_number = 1,
                          length_decay = 0.7,
                          min_length = 0.25,
                          generation_max = 2) {
      
      
      self$line_length = line_length
      self$angle_delta = angle_delta
      self$angle_current = angle_current
      self$length_decay = length_decay
      self$min_length = min_length
       self$initiator <- initiator
      self$generators <- generators
      self$generator_number <- generator_number
      self$generation_max <- generation_max
      self$generation_current <- 0
      self$angle_delta <- angle_delta
      
      self$start_x <- 0
      self$start_y <- 0
      self$type <- "F"
      
      self$fractal_lines = fractal_l_generate$new(
        start_x = self$start_x,
        start_y = self$start_y,
        angle_delta = self$angle_delta,
        angle_current = self$angle_current,
        line_length = self$line_length,
        length_decay = self$length_decay,
        min_length = self$min_length,
        type = self$type,
        initiator = self$initiator,
        generators = self$generators,
        generator_number <- self$generator_number,
        generation_current = self$generation_current,
        generation_max = self$generation_max
      )$fractal_lines
    },
    plotf = function() {
      ggplot(self$fractal_lines, aes(x, y, group = id, color = line_color)) +
        geom_line() +
        scale_color_identity() +
        guides(color = FALSE, linetype = FALSE) +
        theme_void()
    }
  ) # public
)  # fractal_l


# Fractal instructions


l_names = c(
  'page 8',
  'page 9a',
  'page 9b',
  'page 9c',
  'page 10a',
  'page 10b',
  'page 10c',
  'page 10d',
  'page 10e',
  'page 10f',
  'page 11a',
  'page 11b',
  'page 12a',
  'page 12b',
  'page 2 Koch Snowflake',
  'Tree'
)
l_initiators = c(
  'F-F-F-F',
  'F-F-F-F',
  '-F',
  '-F+F+F+F',
  'F-F-F-F',
  'F-F-F-F',
  'F-F-F-F',
  'F-F-F-F',
  '-F-F-F-F',
  'F-F-F-F',
  '-L',
  '---R',
  '++L',
  '-R',
  'F++F++F',
  'T'
)
l_F_generators = c(
  'F-F+F+FF-F-F+F',
  'F+FF-FF-F-F+F+FF-F-F+F+FF+FF-F',
  'F+F-F-F+F',
  'F+f-FF+F+FF+Ff+FF-f+FF-F-FF-Ff-FFF',
  'FF-F-F-F-F-F+F',
  'FF-F-F-F-FF',
  'FF-F+F-F-FF',
  'FF-F--F-F',
  'F-FF--F-F',
  'F-F+F-F-F',
  'L+R+',
  'R+L+R',
  'L+R++R-L--LL-R+',
  'LL-R-R+L+L-R-RL+R+LLR-L+R+LL+R-LR-R-L+L+RR-',
  'F-F++F-F',
  'B'
)
l_f_generators = c('',
                   '',
                   '',
                   'ffffff',
                   '',
                   '',
                   '',
                   '',
                   '',
                   '',
                   '-L-R',
                   'L-R-L',
                   '-L+RR++R+L--L-R',
                   '+LL-R-R+L+LR+L-RR-L-R+LRR-L-RL+L+R-R-L+L+RR',
                   '',
                   '')
l_angles = c(90,
             90,
             90,
             90,
             90,
             90,
             90,
             90,
             90,
             90,
             90,
             60,
             60,
             90,
             60,
             17)

l_start_angles = c(90,
                   90,
                   90,
                   90,
                   90,
                   90,
                   90,
                   90,
                   90,
                   90,
                   90,
                   180,
                   90,
                   90,
                   0,
                   90)




l_systems <-
  data.frame(l_names,
             l_initiators,
             l_F_generators,
             l_f_generators,
             l_angles,
             l_start_angles)

plot_fractal_l <- function(l_systems_row_num, l_systems_max_generation){
  

  lSystem = fractal_l$new(
    line_length = 10,
    angle_delta = l_systems$l_angles[l_systems_row_num],
    angle_current = l_systems$l_start_angles[l_systems_row_num],
    initiator = l_systems$l_initiators[l_systems_row_num],
    generators = c(l_systems$l_F_generators[l_systems_row_num], l_systems$l_f_generators[l_systems_row_num]),
    generator_number = 1,
    length_decay = 0.7,
    min_length = 0.25,
    generation_max = l_systems_max_generation
  )
  lSystem$plotf()
}

Koch Snowflake

Generation n = 4
Angle = 60
Initiator = F++F++F
Generators = F-F++F-F
page 2 Koch Snowflake

Tree

Generation n = 8
Angle = 17
Initiator = T
Generators = B
Tree