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

Tuesday 6 July 2010

3D Gears: (exploring ruby 1.9 syntax, ruby-opengl bindings, lambda & Struct)

Recently I have been exploring a couple of Scheme implementations of cfdg, one of those used opengl as a back end. That got me interested in the idea of exploring ruby-opengl bindings and this is the result (I found this rather old example of glxgears by Arto Bendiken, which was in need of updating anyway, the array entry for color and light attributes are now Struct rather than array). I got a bit carried away as I've replaced the callback methods with new stabby ruby lambda syntax, anyway result is rather impressive. Main thing bugging is I can't give the window a title (the escape key now works for me, since I replace 27 with ?\e).


#!/usr/bin/env ruby1.9
# coding: utf-8

# 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
# 2010-06-05 Revised Ruby Version Monkstone tested on AMD 64 bit linux with nvidia driver
# using lib-opengl-ruby1.9 (ie not the gem version)

require 'opengl'

class Gears
  attr_reader :autoexit, :frames, :t0_idle, :t0
  
  Position = Struct.new("Position", :x, :y, :z, :w)   # Struct now replace Array
  Color = Struct.new("Color", :red, :green, :blue, :alpha)
  
  POS = Position.new(5.0, 5.0, 10.0, 0.0)  
  RED = Color.new(0.8, 0.1, 0.0, 1.0)
  GREEN = Color.new(0.0, 0.8, 0.2, 1.0)
  BLUE = Color.new(0.2, 0.2, 1.0, 1.0)
  
  include GL
  include GLUT
  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

    ShadeModel(GL::FLAT)

    Normal(0.0, 0.0, 1.0)

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

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

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

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

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

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

    Enable(GL::NORMALIZE)

   ARGV.each do |arg|
      case arg
        when '-info'
          puts "GL_RENDERER   = #{GetString(GL::RENDERER)}"
          puts "GL_VERSION    = #{GetString(GL::VERSION)}"
          puts "GL_VENDOR     = #{GetString(GL::VENDOR)}"
          puts "GL_EXTENSIONS = #{GetString(GL::EXTENSIONS)}"
        when '-exit'
          @autoexit = 30
          puts "Auto Exit after #{@autoexit} seconds. " 
      end
    end
  end

  def initialize
    Init()
    InitDisplayMode(GLUT::RGB | GLUT::DEPTH | GLUT::DOUBLE)
    InitWindowPosition(0, 0)
    InitWindowSize(300, 300)    
    CreateWindow([]) # want's C char array as entry
    init()
    glut_callbacks()
    DisplayFunc(@draw)
    ReshapeFunc(@reshape)
    KeyboardFunc(@key)
    SpecialFunc(@special)
    VisibilityFunc(@visible)
    MouseFunc(@mouse)
    MotionFunc(@motion)
  end

  def start
    MainLoop()
  end
  
end

private
 
def glut_callbacks  
        # define GLUT callbacks as ruby 1.9 lambdas (could use Proc.new)
 
  @idle = ->{
    t = Get(GLUT::ELAPSED_TIME) / 1000.0
    @t0_idle = t unless t0_idle
    # 90 degrees per second
    @angle += 70.0 * (t - t0_idle)
    @t0_idle = t
    PostRedisplay()
   }
    
  @visible = -> vis {
    IdleFunc(vis == GLUT::VISIBLE ? @idle : nil)
  }

  @mouse = -> button, state, x, y {
    @mouse = state
    @x0, @y0 = x, y
  }
  
  @motion = -> x, y {
    if @mouse == GLUT::DOWN
      @view_roty += @x0 - x
      @view_rotx += @y0 - y
    end
    @x0, @y0 = x, y
  }
    
  # begin draw
  @draw = ->{
    Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT);

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

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

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

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

    PopMatrix()

    SwapBuffers()

    @frames = 0 unless frames
    @t0 = 0 unless t0

    @frames += 1
    t = Get(GLUT::ELAPSED_TIME)
    if t - t0 >= 5000
      seconds = (t - t0) / 1000.0
      fps = frames / seconds
      puts sprintf("%d frames in %6.3f seconds = %6.3f FPS", frames, seconds, fps)
      @t0, @frames = t, 0
      autoexit.nil? ? nil : (t <= 999.0 * autoexit) ? nil : exit(0)
    end
  }
  
  # end draw
  
  # Change view angle, exit upon ESC
  @key = -> k, x, y {
    case k
      when ?z
        @view_rotz += 5.0
      when ?Z
        @view_rotz -= 5.0
      when ?\e 
        exit(0)
    end
    PostRedisplay()
  }

  # Change view angle
  @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
    PostRedisplay()
  }
  
  @reshape = -> width, height{
    h = height.to_f / width
    Viewport(0, 0, width, height)
    MatrixMode(GL::PROJECTION)
    LoadIdentity()
    Frustum(-1.0, 1.0, -h, h, 5.0, 60.0)
    MatrixMode(GL::MODELVIEW)
    LoadIdentity()
    Translate(0.0, 0.0, -40.0)
  }  
end 

fred = Gears.new
#fred.SetWindowTitle( ###some cstring### ) # how?
fred.start




Unlike ruby-processing there is absolutely no problem resizing (on linux), indeed you can and should experiment with resizing the frame whilst the animation is running. Also the syntax is not too bad makes me think I should explore it a bit more!!!

No comments:

Post a Comment

Followers

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