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 Anderson

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