#!/usr/bin/env ruby # By T. Kowaliw, 2014, http://kowaliw.ca # Feel free to use, modify, copy, distribute, sell, abuse, exploit, enslave, ravage, whatevs, provided you create one (1) piece of graffiti in my honour in the major city of your choice. Proof required. require 'colormath' class VirgiCell include Math, ColorMath attr_accessor :x, :y def initialize(parent, x, y, size, direction, hue, saturation, brightness, iter_num) @parent = parent @x = x @y = y @size = size @direction = direction @brightness = brightness @hue = hue @saturation = saturation @iter_num = iter_num end def children the_die = rand new_b = [@brightness + 0.005, 0.8].min new_h = @hue + rand*0.01 - 0.005 return [] if @size < 1.2 && rand < 0.2 # kill small branches the_die -= 0.1 if @size < 1.5 # less small branches from small brances # most of the time, we will just add another child in a direct line from the parent... if the_die < 0.55 || @iter_num < 2 immediate_direction = @direction immediate_direction = @direction + rand*3.14 - 1.57 if rand < 0.6 # generate a different immediate direction for gnarls size_mult = @size*rand*3 size_mult = @size*4.0 if rand < 0.1 && @iter_num < 5 new_x = @x + cos(immediate_direction)*size_mult new_y = @y + sin(immediate_direction)*size_mult new_direction = @direction new_direction = new_direction + (rand*0.2 - 0.1) if rand < 0.4 new_direction = new_direction + (rand*0.6 - 0.3) if rand < 0.05 new_size = [@size + rand*0.2 - 0.12, 1.0].max new_size = [@size +rand*2.0 - 1.2 , 1.0].max if rand < 0.05 return [VirgiCell.new(self, new_x, new_y, new_size, new_direction, new_h, @saturation, new_b, @iter_num+1)] # sometimes, let's divide into equal sized branches elsif the_die < 0.75 new_direction_1 = @direction + 0.3 new_direction_2 = @direction - 0.3 new_x_1 = @x + cos(new_direction_1)*@size*3 new_y_1 = @y + sin(new_direction_1)*@size*3 new_x_2 = @x + cos(new_direction_2)*@size*3 new_y_2 = @y + sin(new_direction_2)*@size*3 return [ VirgiCell.new(self, new_x_1, new_y_1, [@size*0.75, 1.0].max, new_direction_1, new_h, @saturation, new_b, @iter_num+1), VirgiCell.new(self, new_x_2, new_y_2, [@size*0.75, 1.0].max, new_direction_2, @hue, @saturation, new_b, @iter_num+1) ] # sometimes, let's shoot off a tiny branch in one direction elsif the_die < 0.96 new_direction_1 = @direction + rand*0.05 - 0.025 new_direction_2 = @direction - rand*1.27 - 0.3 new_direction_2 = @direction + rand*1.27 + 0.3 if rand < 0.5 new_x_1 = @x + cos(new_direction_1)*@size*3 new_y_1 = @y + sin(new_direction_1)*@size*3 new_x_2 = @x + cos(new_direction_2)*@size*4 new_y_2 = @y + sin(new_direction_2)*@size*4 return [ VirgiCell.new(self, new_x_1, new_y_1, @size, new_direction_1, new_h, @saturation, new_b, @iter_num+1), VirgiCell.new(self, new_x_2, new_y_2, [(rand*2 + 1),@size/3].min, new_direction_2, @hue, @saturation, new_b, @iter_num+1) ] end # and the rest of the time, let's just kill the whole branch. return [] end # return a line to the joint (if one exists) and then a ball at the joint. def to_svg # for some idiot reason, the HSL spec calls for H in [0, 360], and the other params in [0,1]. the_colour = ColorMath::HSL.new(@hue*360.0, @saturation, @brightness).hex rect_str = "" if @parent return "#{rect_str} " end end class VirgiTree def initialize(startX, startY, num_iterations, h, s, l, size) @finished_cells = Array.new # start with a single cell at the bottom centre pointing "up" (NB: svg canvas has zero at the top) @to_process_cells = [VirgiCell.new(nil, startX, startY, size, -1.57079, h, s, l, 0)] # for twenty iterations, process cells and add the processed ones to the "finished" queue 1..num_iterations.times do new_to_process_cells = Array.new @to_process_cells.each do |curr_cell| @finished_cells.push(curr_cell) curr_cell.children.each { |kid| new_to_process_cells.push(kid) } end @to_process_cells = new_to_process_cells end # reverse so that last added cells are drawn first. Then lines will always be drawn before circles. @finished_cells = @finished_cells.concat(@to_process_cells).reverse end def to_svg ret_str = "" @finished_cells.each {|curr_cell| ret_str = ret_str + curr_cell.to_svg + "\n"} return ret_str end end # now output the SVG code puts "" puts "" puts VirgiTree.new(140, 490, 35, 0.0, 0.5, 0.1, 14.0).to_svg puts ""