Lenny Explorer after Leander Herzog
A homage to Leander Herzog ‘The Explorer’
After a version by Karsten Schmidt (aka toxi)
The sketch demonstrates the use of polar coordinates for steering and simplified Line2D instances for finding line intersections.
-
The underlying process of the piece is as follows: update steering direction, move forward if no existing line is being crossed, else search for a new direction
-
Featuring toxiclibs Vec2D classes (renamed TVec2D in the gem version)
-
Featuring toxiclibs Circle and Rect classes
-
Featuring custom Path, Boundary and Line2D classes
The Sketch
# lenny_explorer.rb
require 'toxiclibs'
java_import 'toxi.geom.Circle'
java_import 'toxi.geom.Rect'
load_library :lenny
attr_reader :path
def settings
size(600, 600)
end
def setup
sketch_title 'Lenny Explorer'
no_fill
@path = Path.new(
RectBoundary.new(Rect.new(10, 10, width - 20, height - 20)),
10,
0.03,
3_000
)
# @path = Path.new(
# CircularBoundary.new(Circle.new(Vec2D.new(width / 2, height / 2), 250)),
# 10,
# 0.03,
# 3_000
# )
end
def draw
background(255)
50.times { path.grow }
path.render(g)
end
Custom Boundary classes
# Duck typing Boundary
class CircularBoundary
attr_reader :circle
def initialize(circle)
@circle = circle
end
def contains_point(vec)
circle.contains_point(vec)
end
def centroid
circle # Circle can DuckType as Vec2D
end
end
# Duck typing Boundary
class RectBoundary
attr_reader :rectangle
def initialize(rectangle)
@rectangle = rectangle
end
def contains_point(vec)
rectangle.contains_point(vec)
end
def centroid
rectangle.get_centroid
end
end
Custom Line2D
class Line2D
attr_reader :a, :b
def initialize(a, b)
@a = a
@b = b
end
def intersecting?(line)
denom = (line.b.y - line.a.y) * (b.x - a.x) - (line.b.x - line.a.x) * (b.y - a.y)
na = (line.b.x - line.a.x) * (a.y - line.a.y) - (line.b.y - line.a.y) * (a.x - line.a.x)
nb = (b.x - a.x) * (a.y - line.a.y) - (b.y - a.y) * (a.x - line.a.x)
return false if denom.zero?
ua = na / denom
ub = nb / denom
(ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)
end
end
The path class
# Stores and manipulates the path
class Path
attr_reader :path, :last, :bounds, :cut, :theta, :delta, :speed, :searches
def initialize(bounds, speed, delta, history)
@bounds = bounds
@speed = speed
@delta = delta
@theta = 0
@path = (0..history).map { bounds.centroid.copy }
@searches = 0
@last = TVec2D.new(path.first)
end
def grow
@delta = rand(-0.2..0.2) if rand < 0.1
if !intersecting?
move
else
search
end
end
def move
@last = path.first
path.pop
@theta += delta
path.unshift last.add(TVec2D.new(speed, theta).cartesian)
@searches = 0
end
def search
@theta += delta
path[0] = last.add(TVec2D.new(speed, theta).cartesian)
@searches += 1
end
def render(gfx)
gfx.begin_shape
path.map { |vec| gfx.curve_vertex(vec.x, vec.y) }
gfx.end_shape
end
def intersecting?
return true unless bounds.contains_point(path.first)
if searches < 100
a = Line2D.new(path[0], path[1])
(3...path.length).each do |i|
b = Line2D.new(path[i], path[i - 1])
return true if a.intersecting?(b)
end
end
false
end
end