The Golden Fibonacci Ratio (#69)

Author: Shane E.

Quiz 69: The Golden Fibonacci Ratio.

I decided for this quiz to try and use the curses library to make

screen placement easier. I think it turned out well, though I am sure

there are more clever ways of achieving the same results. Press the

‘enter’ key to advance through each successive iteration of the

algorithm. Five is the optimal number of times to iterate for

standard

consoles.

usage: ruby quiz69.rb [optional: number of times to add sqr]

require ‘curses’

class Square

attr_reader :height, :width
attr_writer :height, :width

def initialize( screen, length = 1, width = 1 )
    @screen, @height, @width = screen, length, width
end

def max_side
    @height >= @width ? @height : @width
end

def add_to_screen
    @screen.setpos( 0, 0 )
    ( 0 .. @width + 1 ).each { @screen.addstr( '#' ) }
    ( 1 .. @height ).each do |height|
        @screen.setpos( height, 0 )
        @screen.addstr( '#' )
        @screen.setpos( height, @width + 1 )
        @screen.addstr( '#' )
    end
    @screen.setpos( @height + 1, 0 )
    ( 0 .. @width + 1 ).each { @screen.addstr( '#' ) }
    @screen.getch
end

end

add_to = :right
sqr = Square.new( Curses::init_screen )
sqr.add_to_screen

times = ARGV[ 0 ] || 5
( 1 … times.to_i ).each do |x|

if add_to == :right
    sqr.width  += sqr.max_side + 1
else
    sqr.height += sqr.max_side + 1
end

add_to = add_to == :right ? :bottom : :right
sqr.add_to_screen

end

I’ve never done OpenGL before. So after looking a some examples here’s
what I came up with. I did this pretty quick so there are many things
that ought to be fixed (like the mess of global variables), but it gets
the job done. Like the curses submission, pressing enter repeatedly
will add the next iteration. I don’t have automatic resizing yet so
after you press enter, to get it to look right you have to resize the
window. Maybe I’ll think about that a bit later (suggestions are
welcome on how resizing should be done). Anyways thanks for the quiz!

-----Jay A.

require ‘opengl’
require ‘glut’

class Array
def rotate!
push shift
end
def rotate
self.dup.rotate!
end
end

key_func = lambda do |key,x,y|
case key
when ?Q, ?q
exit 0
when ?\r, ?\n
side = $width>$height ? $width : $height
sq_x, sq_y = $x, $y
case $add_to.first
when :right
sq_x = $x + $width
$width += side
when :left
sq_x = $x - side
$x = sq_x
$width += side
when :top
sq_y = $y + $height
$height += side
when :bottom
sq_y = $y - side
$y = sq_y
$height += side
end
$squares << {
:side => side,
:x => sq_x,
:y => sq_y,
:color => [rand, rand, rand]
}

    $add_to.rotate!
end
GLUT.PostRedisplay

end

reshape_func = lambda do |w,h|
$screen_width = w
$screen_height = h
range = ($width>$height ? $width : $height)
border = range * 0.05
left, right = $x-border, $x+range+border
bottom, top = $y-border, $y+range+border
aspect = w.to_f/h.to_f
if w <= h then
bottom /= aspect
top /= aspect
else
left *= aspect
right *= aspect
end
GL.Viewport(0, 0, w, h)
GL.MatrixMode GL::PROJECTION
GL.LoadIdentity
GLU::Ortho2D(left, right, bottom, top)
GL.MatrixMode GL::MODELVIEW
end

display_func = lambda do
GL.Clear GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT

$squares.each do |s|
    GL.Translate(s[:x], s[:y], 0.0)
    side = s[:side]
    GL.Begin(GL::QUADS)
    GL.Color(*s[:color])
    GL.Vertex(0.0, 0.0)
    GL.Vertex(side, 0.0)
    GL.Vertex(side, side)
    GL.Vertex(0.0, side)
    GL.End
    GL.Translate(-s[:x], -s[:y], 0.0)
end

GL.Flush

end

GLUT.Init
GLUT.InitDisplayMode GLUT::SINGLE | GLUT::RGB | GLUT::DEPTH
GLUT.CreateWindow “Fibonacci”
GL.FrontFace GL::CW
GL.Enable(GL::DEPTH_TEST)

$screen_width = GLUT.Get(GLUT::WINDOW_WIDTH)
$screen_height = GLUT.Get(GLUT::WINDOW_HEIGHT)
$squares = []
$squares << {
:side => 1.0,
:x => 0.0,
:y => 0.0,
:color => [rand, rand, rand]
}
$width = 1.0
$height = 1.0
$x = 0.0
$y = 0.0
$add_to = [:right, :bottom, :left, :top]

GLUT.KeyboardFunc key_func
GLUT.ReshapeFunc reshape_func
GLUT.DisplayFunc display_func

GLUT.MainLoop

Here’s my attempt at the quiz. Instead of producing a text output I
tried myself at SVG. The first version I came up with didn’t use any
special SVG commands and takes care of finding out the positions for
each square by itself. After seeing the Postscript solution I looked in
the SVG specs for something similar… the result is the second version,
which seems a bit more unclear for human beings but makes the algorithm
much easier (no more “case @side”)

So here they are…

First version with explicit placement of squares:

require ‘rubygems’
require ‘svg/svg’
require ‘memoize’

module GoldenRectangle
COLORS = [‘red’, ‘green’, ‘blue’, ‘yellow’]
SEED_POS = {:x => 0, :y => 0}

    class PreSeedSquare
            def height
                    0
            end

            def x
                    SEED_POS[:x]
            end

            def y
                    SEED_POS[:y]
            end

            def color
                    COLORS[0]
            end

            def parent
                    @parent
            end
    end

    class SeedSquare < PreSeedSquare
            def initialize
                    @parent = PreSeedSquare.new
            end

            def fib(n)
                    a, b = 1, 1
                    n.times { a, b = b, a + b }
                    a
            end

            def size
                    fib(level)
            end

            def level
                    0
            end

            def width
                    size
            end

            alias_method :height, :width

            def side
                    :top
            end

            def color
                    COLORS[1]
            end

            def as_spiral
                    ["M#{x},#{y + height}", "A#{width},#{height} 0

0,1 #{x + width},#{y}"]
end

            def as_squares
                    [svg_square]
            end

            def svg_square
                    col = self.color
                    SVG::Rect.new(x, y, width, height) { self.style

= SVG::Style.new(:fill => col, :opacity => 0.5) }
end
end

    class Square < SeedSquare
            include Memoize

            attr_reader :parent

            def initialize(parent)
                    @parent = parent
                    @grandparent = @parent.parent

                    # without memoization this implementation

wouldn’t be feasible
[:parent, :color, :size, :side, :x, :y].each
{|f| memoize f }
end

            class << self
                    alias_method :attach_to, :new
            end

            def as_squares
                    @parent.as_squares << svg_square
            end

            def as_spiral
                    @parent.as_spiral << "A#{width},#{height} 0 0,1

" + spiral_point.join(",")
end

            def color
                    if @grandparent.parent.nil?
                            COLORS[2]
                    elsif
                            @grandparent.parent.parent.nil?
                            COLORS[3]
                    else
                            (COLORS -

[@parent.parent.parent.parent.color, @parent.parent.parent.color,
@parent.color]).first
end
end

            def level
                    @parent.level + 1
            end

            def side
                    case @parent.side
                    when :right
                            :bottom
                    when :bottom
                            :left
                    when :left
                            :top
                    when :top
                            :right
                    end
            end

            def x
                    case side
                    when :right
                            @parent.x + @parent.width
                    when :bottom
                            @grandparent.x
                    when :left
                            @parent.x - width
                    when :top
                            @parent.x
                    end
            end

            def y
                    case side
                    when :left
                            @grandparent.y
                    when :bottom
                            @parent.y + @parent.height
                    when :top
                            @parent.y - height
                    when :right
                            @parent.y
                    end
            end

            def spiral_point
                    case side
                    when :right
                            [x + width, y + height]
                    when :left
                            [x, y]
                    when :top
                            [x + width, y]
                    when :bottom
                            [x, y + height]
                    end
            end
    end

end

def fixpoint(start, limit, &blk)
limit.times do
start = blk.call(start)
end
start
end

iterations = ARGV.shift || 17

the golden rectangle is the fixpoint of the function that attaches a

square

to a golden-rectangle-approximation

we start with a 1x1 seed

the_golden_rectangle = fixpoint(GoldenRectangle::SeedSquare.new,
iterations) do |rectangle|
GoldenRectangle::Square.attach_to rectangle
end

svg = SVG.new(‘1024’, ‘768’, ‘-550 -300 1024 768’)
svg << the_golden_rectangle.as_squares
svg << SVG::Path.new(the_golden_rectangle.as_spiral) {
self.style = SVG::Style.new(:fill => ‘none’, :stroke => ‘#000’,
:stroke_width => 1, :stroke_opacity => 1.0)
}
puts svg.to_s

###############
Second solution involving rotate & translate

require ‘rubygems’
require ‘svg/svg’
require ‘memoize’

module GoldenRectangle
COLORS = [‘red’, ‘green’, ‘blue’, ‘yellow’]
SEED_POS = {:x => 0, :y => 0}

    class PreSeedSquare
            def height
                    0
            end

            def x
                    SEED_POS[:x]
            end

            def y
                    SEED_POS[:y]
            end

            def color
                    COLORS[0]
            end

            def parent
                    @parent
            end
    end

    class SeedSquare < PreSeedSquare
            def initialize
                    @parent = PreSeedSquare.new
            end

            def fibo(n)
                    a, b = 1, 1
                    n.times { a, b = b, a + b}
                    a
            end

            def size
                    fibo(level)
            end

            def width
                    size
            end

            def level
                    0
            end

            alias_method :height, :width

            def color
                    COLORS[1]
            end

            def as_spiral
                    ["M#{x},#{y + height}", "A#{width},#{height} 0

0,1 #{x + width},#{y}"]
end

            def as_squares
                    svg_square
            end

            def svg_square
                    col = self.color
                    group = SVG::Group.new
                    group.transform = "rotate(90)

translate(#{-width},#{-height})"
group.id = level
group << SVG::Rect.new(0, 0, width, height) {
self.style = SVG::Style.new(:fill => col, :opacity => 0.5) }

                    path = ["M#{0},#{0}", "A#{width},#{height} 0

0,0 #{width},#{height}"]
group << SVG::Path.new(path) {
self.style = SVG::Style.new(:fill =>
‘none’, :stroke => ‘#000’, :stroke_width => 1, :stroke_opacity => 1.0)
}
group
end
end

    class Square < SeedSquare
            include Memoize

            attr_reader :parent

            def initialize(parent)
                    @parent = parent
                    @grandparent = @parent.parent

                    # without memoization this implementation

wouldn’t be feasible
[:level, :parent, :color, :size].each {|f|
memoize f }
end

            class << self
                    alias_method :attach_to, :new
            end

            def as_squares
                    g1 = @parent.as_squares
                    g2 = svg_square
                    g2 << g1
                    g2
            end

            def color
                    if @grandparent.parent.nil?
                            COLORS[2]
                    elsif
                            @grandparent.parent.parent.nil?
                            COLORS[3]
                    else
                            (COLORS -

[@parent.parent.parent.parent.color, @parent.parent.parent.color,
@parent.color]).first
end
end

            def level
                    @parent.level + 1
            end
    end

end

def fixpoint(start, limit, &blk)
limit.times do
start = blk.call(start)
end
start
end

iterations = ARGV.shift || 17

the golden rectangle is the fixpoint of the function that attaches a

square

to a golden-rectangle-approximation

we start with a 1x1 seed

the_golden_rectangle = fixpoint(GoldenRectangle::SeedSquare.new,
iterations) do |rectangle|
GoldenRectangle::Square.attach_to rectangle
end

svg = SVG.new(‘1024’, ‘768’, ‘2500 -1100 1024 768’)
svg << the_golden_rectangle.as_squares
svg << SVG::Path.new(the_golden_rectangle.as_spiral) {
self.style = SVG::Style.new(:fill => ‘none’, :stroke => ‘#000’,
:stroke_width => 1, :stroke_opacity => 1.0)
}
puts svg.to_s

I fixed a few things and added mouse callbacks (click drag for zoom,
shift-click-drag for panning). I’d still like to figure out how to make
it more OO, but at least my understanding of OpenGL is better.

-----Jay A.

usr/bin/ruby

require ‘opengl’
require ‘glut’

class Array
def rotate!
push shift
end
def rotate
self.dup.rotate!
end
end

mouse_func = lambda do |button,state,x,y|
shift_down = (GLUT.GetModifiers & GLUT::ACTIVE_SHIFT) != 0
case button
when GLUT::LEFT_BUTTON
case state
when GLUT::UP
$mode = :none
when GLUT::DOWN
if shift_down then
$mode = :pan
else
$mode = :zoom
end
$mouse_x = x
$mouse_y = y
end
when 3 #scroll up
if state == GLUT::DOWN then
$zoom *= 1.25
GLUT.PostRedisplay
end
when 4 #scroll down
if state == GLUT::DOWN then
$zoom /= 1.25
GLUT.PostRedisplay
end
end
end

motion_func = lambda do |x,y|
case $mode
when :zoom
$zoom = 1.0 + (y-$mouse_y)/100.0
when :pan
$pan_x += (x-$mouse_x)
$units_per_pixel
$pan_y += ($mouse_y-y)*$units_per_pixel
when :rotate
end
if $mode != :none then
$mouse_x = x
$mouse_y = y
GLUT.PostRedisplay
end
end

key_func = lambda do |key,x,y|
case key
when ?Q, ?q
GLUT.DestroyWindow($window);
exit 0
when ?-
$zoom /= 2.0
when ?+
$zoom *= 2.0
when ?\r, ?\n
side = $width>$height ? $width : $height
sq_x, sq_y = $box_x, $box_y
case $add_to.first
when :right
sq_x = $box_x + $width
$width += side
when :left
sq_x = $box_x - side
$box_x = sq_x
$width += side
when :top
sq_y = $box_y + $height
$height += side
when :bottom
sq_y = $box_y - side
$box_y = sq_y
$height += side
end
$squares << {
:side => side,
:x => sq_x,
:y => sq_y,
:color => [rand, rand, rand]
}

	$add_to.rotate!
end
GLUT.PostRedisplay

end

reshape_func = lambda do |w,h|
h = 1 if h == 0
$screen_width = w
$screen_height = h
GL.Viewport(0, 0, w, h)
GL.MatrixMode GL::PROJECTION
GL.LoadIdentity
GLU.Perspective(45.0, w.to_f/h.to_f, 0.1, 100.0);
GL.MatrixMode GL::MODELVIEW
end

display_func = lambda do
GL.Clear GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT

GL.LoadIdentity
GL.Translate($pan_x, $pan_y, -6.0)
GL.Scale($zoom, $zoom, 0.0)
$squares.each do |s|
	GL.Translate(s[:x], s[:y], 0.0)
	side = s[:side]
	GL.Begin(GL::QUADS)
	GL.Color(*s[:color])
	GL.Vertex(0.0, 0.0)
	GL.Vertex(side, 0.0)
	GL.Vertex(side, side)
	GL.Vertex(0.0, side)
	GL.End
	GL.Translate(-s[:x], -s[:y], 0.0)
end

GLUT.SwapBuffers;

end

GLUT.Init
GLUT.InitDisplayMode(GLUT::DOUBLE | GLUT::RGBA | GLUT::DEPTH)

$screen_width = 640
$screen_height = 480
$units_per_pixel = 1.0/100.0 #TODO: should be determined by screen size
GLUT.InitWindowSize($screen_width, $screen_height)
GLUT.InitWindowPosition(0, 0)

$window = GLUT.CreateWindow “Fibonacci”

GLUT.KeyboardFunc key_func
GLUT.ReshapeFunc reshape_func
GLUT.DisplayFunc display_func
GLUT.MotionFunc motion_func
GLUT.MouseFunc mouse_func
#GLUT.IdleFunc display_func

GL.ClearColor(0.0, 0.0, 0.0, 0.0)
GL.ClearDepth(1.0)
GL.DepthFunc(GL::LESS)
GL.Enable(GL::DEPTH_TEST)
GL.ShadeModel(GL::SMOOTH)

$squares = []
$squares << {
:side => 1.0,
:x => 0.0,
:y => 0.0,
:color => [rand, rand, rand]
}
$width = 1.0
$height = 1.0
$box_x = 0.0
$box_y = 0.0
$pan_x = 0.0
$pan_y = 0.0
$zoom = 1.0
$mode = :none
$add_to = [:right, :bottom, :left, :top]

GLUT.MainLoop