Skip to content

Commit f887408

Browse files
committed
move mycelium to demo
1 parent c12c502 commit f887408

File tree

6 files changed

+337
-0
lines changed

6 files changed

+337
-0
lines changed

demo/library/branch/branch.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
require 'forwardable'
2+
3+
# Here we use the PiCrate Vec2D class, and not toxis Vec2D. We avoid using
4+
# ToxicLibsSupport, by using our own AppRender to translate Vec2D to vertices.
5+
# Further we use the power of ruby (metaprogramming) to make Branch enumerable
6+
# and use Forwardable to define which enumerable methods we want to use.
7+
class Branch
8+
include Enumerable
9+
extend Forwardable
10+
def_delegators(:@children, :<<, :each, :length)
11+
# variance angle for growth direction per time step
12+
THETA = Math::PI / 6
13+
# max segments per branch
14+
MAX_LEN = 100
15+
# max recursion limit
16+
MAX_GEN = 3
17+
# branch chance per time step
18+
BRANCH_CHANCE = 0.05
19+
# branch angle variance
20+
BRANCH_THETA = Math::PI / 3
21+
attr_reader :position, :dir, :path, :children, :xbound, :speed, :ybound, :app
22+
23+
def initialize(app, pos, dir, speed)
24+
@app = app
25+
@position = pos
26+
@dir = dir
27+
@speed = speed
28+
@path = []
29+
@children = []
30+
@xbound = Boundary.new(0, app.width)
31+
@ybound = Boundary.new(0, app.height)
32+
path << pos
33+
end
34+
35+
def run
36+
grow
37+
display
38+
end
39+
40+
private
41+
42+
# NB: use of both rotate! (changes original) rotate (returns a copy) of Vec2D
43+
def grow
44+
check_bounds(position + (dir * speed)) if path.length < MAX_LEN
45+
@position += (dir * speed)
46+
dir.rotate!(rand(-0.5..0.5) * THETA)
47+
path << position
48+
if (length < MAX_GEN) && (rand < BRANCH_CHANCE)
49+
branch_dir = dir.rotate(rand(-0.5..0.5) * BRANCH_THETA)
50+
self << Branch.new(app, position.copy, branch_dir, speed * 0.99)
51+
end
52+
each(&:grow)
53+
end
54+
55+
def display
56+
app.begin_shape
57+
app.stroke(255)
58+
app.no_fill
59+
path.each { |vec| vec.to_vertex(app.renderer) }
60+
app.end_shape
61+
each(&:display)
62+
end
63+
64+
def check_bounds(pos)
65+
dir.x *= -1 if xbound.exclude? pos.x
66+
dir.y *= -1 if ybound.exclude? pos.y
67+
end
68+
end
69+
70+
# we are looking for excluded values
71+
Boundary = Struct.new(:lower, :upper) do
72+
def exclude?(val)
73+
!(lower...upper).cover? val
74+
end
75+
end

demo/library/branch3D/branch3D.rb

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
require 'forwardable'
2+
require_relative './rotator'
3+
4+
class Branch
5+
include Enumerable
6+
extend Forwardable
7+
def_delegators(:@children, :<<, :each, :length)
8+
# variance angle for growth direction per time step
9+
THETA = Math::PI / 6
10+
# max segments per branch
11+
MAX_LEN = 100
12+
# max recursion limit
13+
MAX_GEN = 3
14+
# branch chance per time step
15+
BRANCH_CHANCE = 0.05
16+
# branch angle variance
17+
BRANCH_THETA = Math::PI / 4
18+
attr_reader :position, :dir, :path, :children, :xbound, :speed, :ybound, :app, :zbound
19+
20+
def initialize(app, pos, dir, speed)
21+
@app = app
22+
@position = pos
23+
@dir = dir
24+
@speed = speed
25+
@path = []
26+
@children = []
27+
@xbound = Boundary.new(-600, 600)
28+
@ybound = Boundary.new(-150, 150)
29+
@zbound = Boundary.new(-150, 150)
30+
path << pos
31+
end
32+
33+
def run
34+
grow
35+
display
36+
end
37+
38+
private
39+
40+
def grow
41+
check_bounds(position + (dir * speed))
42+
@position += (dir * speed)
43+
(0..2).each do |axis|
44+
Rotate.axis!(axis, dir, rand(-0.5..0.5) * THETA)
45+
end
46+
path << position
47+
if (length < MAX_GEN) && (rand < BRANCH_CHANCE)
48+
branch_dir = dir.copy
49+
(0..2).each do |axis|
50+
Rotate.axis!(axis, branch_dir, rand(-0.5..0.5) * BRANCH_THETA)
51+
end
52+
self << Branch.new(app, position.copy, branch_dir, speed * 0.99)
53+
end
54+
each(&:grow)
55+
end
56+
57+
def display
58+
app.begin_shape
59+
app.stroke 255
60+
app.stroke_weight 0.5
61+
app.no_fill
62+
path.each { |vec| vec.to_vertex(app.renderer) }
63+
app.end_shape
64+
each(&:display)
65+
end
66+
67+
def check_bounds(pos)
68+
dir.x *= -1 if xbound.exclude? pos.x
69+
dir.y *= -1 if ybound.exclude? pos.y
70+
dir.z *= -1 if zbound.exclude? pos.z
71+
end
72+
end
73+
74+
# we are looking for excluded values
75+
Boundary = Struct.new(:lower, :upper) do
76+
def exclude?(val)
77+
true unless (lower...upper).cover? val
78+
end
79+
end

demo/library/branch3D/rotator.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Rotator module is required because rotate is not implemented for
2+
# PiCrate 3D. Here we use Vec2D.rotate! to do Euclid rotation about an
3+
# axis (we can then apply the rotation to each axis in turn)
4+
# NB: we use quaternions in ArcBall (to avoid gimbal lock)
5+
module Rotate
6+
def self.axis!(axis, vec, theta)
7+
array = vec.to_a
8+
array.slice! axis
9+
other = Vec2D.new(*array).rotate! theta
10+
case axis
11+
when 0 # xaxis
12+
vec.y = other.x
13+
vec.z = other.y
14+
when 1 # yaxis
15+
vec.x = other.x
16+
vec.z = other.y
17+
else # zaxis, by default
18+
vec.x = other.x
19+
vec.y = other.y
20+
end
21+
vec
22+
end
23+
end

demo/mycelium.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require 'picrate'
2+
3+
# Simple recursive branching system inspired by mycelium growth
4+
# After an original by Karsten Schmidt
5+
# The vanilla processing sketch was part of the SAC 2013 workshop project
6+
# translated to PiCrate by Martin Prout 2018
7+
class Mycelium < Processing::App
8+
load_library :branch
9+
10+
attr_reader :renderer, :root
11+
12+
def setup
13+
@renderer = AppRender.new(self)
14+
@root = Branch.new(
15+
self,
16+
Vec2D.new(0, height / 2),
17+
Vec2D.new(1, 0),
18+
10.0
19+
)
20+
end
21+
22+
def draw
23+
background(0)
24+
root.run
25+
end
26+
27+
def settings
28+
full_screen
29+
end
30+
end
31+
32+
Mycelium.new

demo/mycelium_box.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
require 'picrate'
2+
require 'arcball'
3+
# Simple recursive branching system inspired by mycelium growth
4+
# After an original by Karsten Schmidt
5+
# The vanilla processing sketch was part of the SAC 2013 workshop project
6+
# translated to PiCrate by Martin Prout 2018
7+
class MyceliumBox < Processing::App
8+
load_library :branch3D
9+
10+
attr_reader :renderer, :root
11+
12+
def setup
13+
sketch_title 'Mycelium Box'
14+
Processing::ArcBall.init self
15+
@renderer = AppRender.new(self)
16+
@root = Branch.new(
17+
self,
18+
Vec3D.new(0, 10, 10),
19+
Vec3D.new(1, 0, 0),
20+
6.0
21+
)
22+
end
23+
24+
def draw
25+
background(0)
26+
no_fill
27+
stroke_weight 2
28+
stroke(200, 0, 0, 100)
29+
box(1200, 300, 300)
30+
root.run
31+
end
32+
33+
def settings
34+
size(1280, 600, P3D)
35+
end
36+
end
37+
38+
MyceliumBox.new

demo/recursive_pentagon.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
require 'picrate'# After an openprocessing sketch by C.Andrews
2+
class RecursivePentagons < Processing::App
3+
attr_reader :strut_factor, :renderer
4+
5+
def setup
6+
sketch_title 'Recursive Pentagons'
7+
@strut_factor = 0.2
8+
@renderer = AppRender.new self # so we can send Vec2D :to_vertex
9+
background(255)
10+
no_loop
11+
end
12+
13+
def draw
14+
translate(width / 2, height / 2)
15+
angle = TWO_PI / 5
16+
radius = width / 2
17+
points = (0...5).map do |i|
18+
x = radius * cos(angle * i)
19+
y = radius * sin(angle * i)
20+
Vec2D.new(x, y)
21+
end
22+
fractal = Pentagon.new(points, 4)
23+
fractal.draw
24+
end
25+
26+
def settings
27+
size(800, 800)
28+
end
29+
end
30+
31+
RecursivePentagon.new
32+
33+
# Here we include Processing::Proxy to mimic vanilla processing inner class
34+
# access.
35+
class Pentagon
36+
include Processing::Proxy
37+
attr_reader :points ,:branches, :level, :midpoints, :innerpoints
38+
39+
def initialize(points, levels)
40+
@points = points
41+
@level = levels
42+
return if level.zero? # so called guard clause in ruby simplifies code
43+
44+
@midpoints = (0...5).map do |i| # build an array of midpoints
45+
midpoint(points[i], points[(i + 1) % 5])
46+
end
47+
@innerpoints = (0...5).map do |i| # build an array of inner points
48+
opposite = points[(i + 3) % 5]
49+
x = midpoints[i].x + (opposite.x - midpoints[i].x) * strut_factor
50+
y = midpoints[i].y + (opposite.y - midpoints[i].y) * strut_factor
51+
Vec2D.new(x, y)
52+
end
53+
# Create the Pentagon objects representing the six inner
54+
# pentagons
55+
# the shape is very regular, so we can build the ring of five
56+
@branches = (0...5).map do |i|
57+
p = [
58+
midpoints[i],
59+
innerpoints[i],
60+
innerpoints[(i + 1) % 5],
61+
midpoints[(i + 1) % 5],
62+
points[(i + 1) % 5]
63+
]
64+
Pentagon.new(p, level - 1)
65+
end
66+
# add the final innermost pentagon
67+
branches << Pentagon.new(innerpoints, level - 1)
68+
end
69+
# This is a simple helper function that takes in two points (as Vec2D) and
70+
# returns the midpoint between them as Vec2D.
71+
def midpoint(point1, point2)
72+
(point2 + point1) * 0.5
73+
end
74+
# This draws the fractal. If this is on level 0, we just draw the
75+
# pentagon formed by the points. When not level 0, iterate through the
76+
# six branches and tell them to draw themselves.
77+
def draw
78+
if level.zero?
79+
no_fill
80+
begin_shape
81+
points.each do |point|
82+
point.to_vertex(renderer)
83+
end
84+
points[0].to_vertex(renderer)
85+
end_shape
86+
else
87+
branches.each(&:draw)
88+
end
89+
end
90+
end

0 commit comments

Comments
 (0)