#!/usr/bin/env ruby

# 3-D gear wheels. This program is in the public domain.
#
# Command line options:
#    -info      print GL implementation information
#    -exit      automatically exit after 30 seconds
#
# 2005-05-01 Ruby version by Arto Bendiken based on gears.c rev 1.8.
# 2005-01-09 Original C version (gears.c) by Brian Paul et al.
# http://cvs.freedesktop.org/mesa/Mesa/progs/demos/gears.c?rev=1.8

require 'opengl'
require 'glut'

class Gears

  POS = [5.0, 5.0, 10.0, 0.0]
  RED = [0.8, 0.1, 0.0, 1.0]
  GREEN = [0.0, 0.8, 0.2, 1.0]
  BLUE = [0.2, 0.2, 1.0, 1.0]

  include Math

  # Draw a gear wheel. You'll probably want to call this function when
  # building a display list since we do a lot of trig here.
  #
  # Input:  inner_radius - radius of hole at center
  #         outer_radius - radius at center of teeth
  #         width - width of gear
  #         teeth - number of teeth
  #         tooth_depth - depth of tooth
  def gear(inner_radius, outer_radius, width, teeth, tooth_depth)
    r0 = inner_radius
    r1 = outer_radius - tooth_depth / 2.0
    r2 = outer_radius + tooth_depth / 2.0

    da = 2.0 * PI / teeth / 4.0

    GL.ShadeModel(GL::FLAT)

    GL.Normal(0.0, 0.0, 1.0)

    # Draw front face
    GL.Begin(GL::QUAD_STRIP)
    for i in 0..teeth
      angle = i * 2.0 * PI / teeth
      GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
      GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
      if i < teeth
        GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
        GL.Vertex3f(r1 * cos(angle + 3 * da),
          r1 * sin(angle + 3 * da), width * 0.5)
      end
    end
    GL.End()
  
    # Draw front sides of teeth
    GL.Begin(GL::QUADS)
    for i in 0...teeth
      angle = i * 2.0 * PI / teeth
      GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
      GL.Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5)
      GL.Vertex3f(r2 * cos(angle + 2 * da),
        r2 * sin(angle + 2 * da), width * 0.5)
      GL.Vertex3f(r1 * cos(angle + 3 * da),
        r1 * sin(angle + 3 * da), width * 0.5)
    end
    GL.End()
  
    GL.Normal(0.0, 0.0, -1.0)
  
    # Draw back face
    GL.Begin(GL::QUAD_STRIP)
    for i in 0..teeth
      angle = i * 2.0 * PI / teeth
      GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
      GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
      if i < teeth
        GL.Vertex3f(r1 * cos(angle + 3 * da),
          r1 * sin(angle + 3 * da), -width * 0.5)
        GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
      end
    end
    GL.End()
  
    # Draw back sides of teeth
    GL.Begin(GL::QUADS)
    for i in 0...teeth
      angle = i * 2.0 * PI / teeth
      GL.Vertex3f(r1 * cos(angle + 3 * da),
        r1 * sin(angle + 3 * da), -width * 0.5)
      GL.Vertex3f(r2 * cos(angle + 2 * da),
        r2 * sin(angle + 2 * da), -width * 0.5)
      GL.Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5)
      GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
    end
    GL.End()
  
    # Draw outward faces of teeth
    GL.Begin(GL::QUAD_STRIP)
    for i in 0...teeth
      angle = i * 2.0 * PI / teeth
      GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
      GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
      u = r2 * cos(angle + da) - r1 * cos(angle)
      v = r2 * sin(angle + da) - r1 * sin(angle)
      len = sqrt(u * u + v * v)
      u /= len
      v /= len
      GL.Normal(v, -u, 0.0)
      GL.Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5)
      GL.Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5)
      GL.Normal(cos(angle), sin(angle), 0.0)
      GL.Vertex3f(r2 * cos(angle + 2 * da),
        r2 * sin(angle + 2 * da), width * 0.5)
      GL.Vertex3f(r2 * cos(angle + 2 * da),
        r2 * sin(angle + 2 * da), -width * 0.5)
      u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da)
      v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da)
      GL.Normal(v, -u, 0.0)
      GL.Vertex3f(r1 * cos(angle + 3 * da),
        r1 * sin(angle + 3 * da), width * 0.5)
      GL.Vertex3f(r1 * cos(angle + 3 * da),
        r1 * sin(angle + 3 * da), -width * 0.5)
      GL.Normal(cos(angle), sin(angle), 0.0)
    end
    GL.Vertex3f(r1 * cos(0), r1 * sin(0), width * 0.5)
    GL.Vertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5)
    GL.End()
  
    GL.ShadeModel(GL::SMOOTH)
  
    # Draw inside radius cylinder
    GL.Begin(GL::QUAD_STRIP)
    for i in 0..teeth
      angle = i * 2.0 * PI / teeth
      GL.Normal(-cos(angle), -sin(angle), 0.0)
      GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
      GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
    end
    GL.End()
  end

  def draw
    GL.Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT);

    GL.PushMatrix()
    GL.Rotate(@view_rotx, 1.0, 0.0, 0.0)
    GL.Rotate(@view_roty, 0.0, 1.0, 0.0)
    GL.Rotate(@view_rotz, 0.0, 0.0, 1.0)

    GL.PushMatrix()
    GL.Translate(-3.0, -2.0, 0.0)
    GL.Rotate(@angle, 0.0, 0.0, 1.0)
    GL.CallList(@gear1)
    GL.PopMatrix()

    GL.PushMatrix()
    GL.Translate(3.1, -2.0, 0.0)
    GL.Rotate(-2.0 * @angle - 9.0, 0.0, 0.0, 1.0)
    GL.CallList(@gear2)
    GL.PopMatrix()

    GL.PushMatrix()
    GL.Translate(-3.1, 4.2, 0.0)
    GL.Rotate(-2.0 * @angle - 25.0, 0.0, 0.0, 1.0)
    GL.CallList(@gear3)
    GL.PopMatrix()

    GL.PopMatrix()

    GLUT.SwapBuffers()

    @frames = 0 if not defined? @frames
    @t0 = 0 if not defined? @t0

    @frames += 1
    t = GLUT.Get(GLUT::ELAPSED_TIME)
    if t - @t0 >= 5000
      seconds = (t - @t0) / 1000.0
      fps = @frames / seconds
      printf("%d frames in %6.3f seconds = %6.3f FPS\n",
        @frames, seconds, fps)
      @t0, @frames = t, 0
      exit if defined? @autoexit and t >= 999.0 * @autoexit
    end
  end

  def idle
    t = GLUT.Get(GLUT::ELAPSED_TIME) / 1000.0
    @t0_idle = t if !defined? @t0_idle
    # 90 degrees per second
    @angle += 70.0 * (t - @t0_idle)
    @t0_idle = t
    GLUT.PostRedisplay()
  end

  # Change view angle, exit upon ESC
  def key(k, x, y)
    case k
      when ?z
        @view_rotz += 5.0
      when ?Z
        @view_rotz -= 5.0
      when 27 # Escape
        exit
    end
    GLUT.PostRedisplay()
  end

  # Change view angle
  def special(k, x, y)
    case k
      when GLUT::KEY_UP
        @view_rotx += 5.0
      when GLUT::KEY_DOWN
        @view_rotx -= 5.0
      when GLUT::KEY_LEFT
        @view_roty += 5.0
      when GLUT::KEY_RIGHT
        @view_roty -= 5.0
    end
    GLUT.PostRedisplay()
  end

  # New window size or exposure
  def reshape(width, height)
    h = height.to_f / width.to_f
    GL.Viewport(0, 0, width, height)
    GL.MatrixMode(GL::PROJECTION)
    GL.LoadIdentity()
    GL.Frustum(-1.0, 1.0, -h, h, 5.0, 60.0)
    GL.MatrixMode(GL::MODELVIEW)
    GL.LoadIdentity()
    GL.Translate(0.0, 0.0, -40.0)
  end

  def init
    @angle = 0.0
    @view_rotx, @view_roty, @view_rotz = 20.0, 30.0, 0.0

    GL.Lightfv(GL::LIGHT0, GL::POSITION, POS)
    GL.Enable(GL::CULL_FACE)
    GL.Enable(GL::LIGHTING)
    GL.Enable(GL::LIGHT0)
    GL.Enable(GL::DEPTH_TEST)

    # Make the gears
    @gear1 = GL.GenLists(1)
    GL.NewList(@gear1, GL::COMPILE)
    GL.Material(GL::FRONT, GL::AMBIENT_AND_DIFFUSE, RED)
    gear(1.0, 4.0, 1.0, 20, 0.7)
    GL.EndList()

    @gear2 = GL.GenLists(1)
    GL.NewList(@gear2, GL::COMPILE)
    GL.Material(GL::FRONT, GL::AMBIENT_AND_DIFFUSE, GREEN)
    gear(0.5, 2.0, 2.0, 10, 0.7)
    GL.EndList()

    @gear3 = GL.GenLists(1)
    GL.NewList(@gear3, GL::COMPILE)
    GL.Material(GL::FRONT, GL::AMBIENT_AND_DIFFUSE, BLUE)
    gear(1.3, 2.0, 0.5, 10, 0.7)
    GL.EndList()

    GL.Enable(GL::NORMALIZE)

    ARGV.each do |arg|
      case arg
        when '-info'
          printf("GL_RENDERER   = %s\n", GL.GetString(GL::RENDERER))
          printf("GL_VERSION    = %s\n", GL.GetString(GL::VERSION))
          printf("GL_VENDOR     = %s\n", GL.GetString(GL::VENDOR))
          printf("GL_EXTENSIONS = %s\n", GL.GetString(GL::EXTENSIONS))
        when '-exit'
          @autoexit = 30
          printf("Auto Exit after %i seconds.\n", @autoexit);
      end
    end
  end

  def visible(vis)
    GLUT.IdleFunc((vis == GLUT::VISIBLE ? method(:idle).to_proc : nil))
  end

  def mouse(button, state, x, y)
    @mouse = state
    @x0, @y0 = x, y
  end
  
  def motion(x, y)
    if @mouse == GLUT::DOWN then
      @view_roty += @x0 - x
      @view_rotx += @y0 - y
    end
    @x0, @y0 = x, y
  end

  def initialize
    GLUT.Init()
    GLUT.InitDisplayMode(GLUT::RGB | GLUT::DEPTH | GLUT::DOUBLE)

    GLUT.InitWindowPosition(0, 0)
    GLUT.InitWindowSize(300, 300)
    GLUT.CreateWindow('Gears')
    init()

    GLUT.DisplayFunc(method(:draw).to_proc)
    GLUT.ReshapeFunc(method(:reshape).to_proc)
    GLUT.KeyboardFunc(method(:key).to_proc)
    GLUT.SpecialFunc(method(:special).to_proc)
    GLUT.VisibilityFunc(method(:visible).to_proc)
    GLUT.MouseFunc(method(:mouse).to_proc)
    GLUT.MotionFunc(method(:motion).to_proc)
  end

  def start
    GLUT.MainLoop()
  end

end

Gears.new.start





syntax highlighted by Code2HTML, v. 0.9.1