Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0

Thursday, 9 January 2014

Penrose tiling generator (after Shiffman)

Previously I have experimented with cfdg and lsystems to generate penrose tiling, this version by Dan Shiffman seems very similar to the cfdg version. Interestingly Dan ported this somewhat from python, and here we end with a ruby version. Dan unsurprisingly makes use of the PVector class to do the vector math here we use my Vec2D from the ruby-processing vecmath library, which is far more concise (designed to use arithmetic operators from the outset). Here is the sketch, somewhat drier than the original with choosable options via control panel (it is necessary to press "reset" to change seed/triangle type):-
# Penrose Tile Generator
# Using a variant of the "ArrayList" recursion technique: http://natureofcode.com/book/chapter-8-fractals/chapter08_section4
# Penrose Algorithm from: http://preshing.com/20110831/penrose-tiling-explained
# Daniel Shiffman May 2013
# Translated (and refactored) to ruby-processing Jan 2014 by Martin Prout

load_libraries :vecmath, :tile, :control_panel
attr_reader :tris, :s, :panel, :hide, :acute

def setup
  size(1024, 576)
  control_panel do |c|
    c.title = "Tiler Control"
    c.look_feel "Nimbus"
    c.checkbox  :seed
    c.checkbox  :acute
    c.button    :generate
    c.button    :reset!
    @panel = c
  end
  @hide = false
  init false # defaults to regular penrose
end

def draw
  # only make control_panel visible once, or again when hide is false
  unless hide
    @hide = true
    panel.setVisible(hide)
  end
  background(255)
  translate(width/2, height/2)
  tris.each do |t|
    t.display
  end
end

def generate
  next_level = []
  tris.each do |t|
    more = t.subdivide
    more.each do |m|
      next_level << m
    end
  end
  @tris = next_level
end

def reset!
  Tiler.acute(acute)  # set the Tiler first
  init @seed
  java.lang.System.gc # but does it do any good?
end

def init alt_seed
  @tris = []
  10.times do |i|     # create 36 degree segments
    a = Vec2D.new
    b = Vec2D.from_angle((2 * i - 1) * PI / 10)
    c = Vec2D.from_angle((2 * i + 1) * PI / 10)
    b *= 370
    c *= 370
    if alt_seed
      tile = (i % 2 == 0)? Tiler.tile(b, a, c) : Tiler.tile(c, a, b)
      tris << tile
    else
      tile = (i % 2 == 0)? Tiler.tile(a, b, c) : Tiler.tile(a, c, b)
      tris << tile
    end
  end
end

Here is the tile library:-
module Tiler
  @@acute = false

  def self.acute(x)
    @@acute = x
  end

  # setup the initial tiling with all red tiles
  def self.tile(a, b, c)
    tile = (@@acute)? ATile.new(0, a, b, c) : Tile.new(0, a, b, c)
  end
end

class Tile
  include Processing::Proxy
  PHI = (1.0 + Math.sqrt(5)) / 2.0 # golden ratio
  RED = [255, 0, 0]
  BLUE = [0, 0, 255]
  COLORS = [RED, BLUE]
  attr_reader :a, :b, :c, :col

  def initialize(col,  a,  b,  c)
    @col, @a, @b, @c = col, a, b, c
  end

  def display
    no_stroke
    fill(*COLORS[col])
    triangle(a.x, a.y, b.x, b.y, c.x, c.y)
    #fill(0,0,255)
    #ellipse(a.x,a.y,4,4)
    #ellipse(b.x,b.y,4,4)
    #ellipse(c.x,c.y,4,4)
  end

  def subdivide
    result = []
    if (col == 0)
      # Subdivide red triangle
      p = b - a
      p /= PHI
      p += a
      result << Tile.new(0, c, p, b)
      result << Tile.new(1, p, c, a)
    else
      # Subdivide blue triangle
      q = a - b
      q /= PHI
      q += b
      r = c - b
      r /= PHI
      r += b
      result << Tile.new(1, r, c, a)
      result << Tile.new(1, q, r, b)
      result << Tile.new(0, r, q, a)
    end
    return result
  end
end

class ATile < Tile
  def subdivide
    result = []
    if (col == 0)
      # Subdivide red (half kite) triangle
      q = b - a
      q /= PHI
      q += a
      r = c - b
      r /= PHI
      r += b
      result << ATile.new(1, r, q, b)
      result << ATile.new(0, q, a, r)
      result << ATile.new(0, c, a, r)
    else
      # Subdivide blue (half dart) triangle
      p = a - c
      p /= PHI
      p += c
      result << ATile.new(1, b, p, a)
      result << ATile.new(0, p, c, b)
    end
    return result
  end
end

Here is a screenshot, before I added second checkbox

No comments:

Post a Comment

Followers

Blog Archive

About Me

My photo
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2