Forum: Ruby The Golden Fibonacci Ratio (#69)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
(Guest)
on 2006-03-05 20:59
(Received via mailing list)
# 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
Jay A. (Guest)
on 2006-03-05 21:30
(Received via mailing list)
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
(Guest)
on 2006-03-05 22:00
(Received via mailing list)
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
Jay A. (Guest)
on 2006-03-09 08:09
(Received via mailing list)
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
This topic is locked and can not be replied to.