Mycelium after Karsten Schmidt
Once upon a time Karsten Schmidt contributed to processing, he created the impressive toxiclibs library, and even ran workshops. Since then he has moved on to even more esoteric thi.ngs. Here is my JRubyArt version of one of his toxiclibs workshop examples, that does without the use of the toxiclibs library, makes use of JRubyArt Vec2D (instead of toxiclibs version) and ruby-meta programming.
See also how I have managed to embed a scaleable video into my own github webpage with markdown.
mycelium.rb (JRubyArt Sketch)
# Simple recursive branching system inspired by mycelium growth
# After an original sketch by Karsten Schmidt
# https://github.com/learn-postspectacular/sac-workshop-2013/blob/master/Mycelium/Mycelium.pde
# translated to JRubyArt by Martin Prout 2018
load_library :branch
attr_reader :renderer, :root
def setup
@renderer = AppRender.new(self)
@root = Branch.new(
self,
Vec2D.new(0, height / 2),
Vec2D.new(1, 0),
10.0
)
end
def draw
background(0)
root.run
save_frame(data_path('#######.png'))
end
def settings
full_screen
end
branch.rb :branch library
require 'forwardable'
# Here we use the JRubyArt Vec2D class, and not toxis Vec2D. We avoid using
# ToxicLibsSupport, by using our own AppRender to translate Vec2D to vertices.
# Further we use the power of ruby (metaprogramming) to make Branch enumerable
# and use Forwardable to define which enumerable methods we want to use.
class Branch
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(app, pos, dir, speed)
@app = app
@position = pos
@dir = dir
@speed = speed
@path = []
@children = []
@xbound = Boundary.new(0, app.width)
@ybound = Boundary.new(0, app.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(app, position.copy, branch_dir, speed * 0.99)
end
each(&:grow)
end
def display
app.begin_shape
app.stroke(255)
app.no_fill
path.each { |vec| vec.to_vertex(app.renderer) }
app.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)
true unless (lower...upper).cover? val
end
end