OpenGL 3D gears demo for Ruby

Discussion in 'Ruby' started by Arto Bendiken, May 1, 2005.

  1. ------=_Part_967_24960416.1114927351434
    Content-Type: text/plain; charset=ISO-8859-1
    Content-Transfer-Encoding: quoted-printable
    Content-Disposition: inline

    Hi there,

    I'm a recent convert from Python, PHP et al., and aside from more
    serious web-related development with Rails, have been playing a bit
    with Yoshi's OpenGL/GLUT bindings. I was interested to see how
    ruby-opengl performance would compare with C/C++, so I wrote a Ruby
    version of the (in)famous spinning 3D gear wheels demo program, which
    is often used as a simple benchmark for OpenGL.

    (Kudos to Yoshi for the OpenGL bindings: translating from the original
    C code was straightforward and only took an hour or so to finish.)

    The results were a bit surprising, in a good way. On my Linux computer
    with ATI Radeon 9700, the compiled version of the original gears.c
    gives an average 1271 FPS, whereas the gears.rb version achieves an
    average of 1203 FPS. (Both programs were run for a couple of minutes
    and results averaged.) That's only a 6% gap in performance.

    Granted this is a fairly simple OpenGL app, but it's certainly easy to
    imagine how productive programmers of open-source games, simulations
    etc. could be writing Ruby instead of C/C++ as is the norm.

    I've attached the Ruby file gears.rb. It only requires the ruby-opengl
    extension to run. The code was translated from rev 1.8 of the
    GLUT-based gears.c (not the same as glxgears!) included in the Mesa 3D
    software package, keeping the structure close to the original, but
    wrapping it in a Ruby class. I also added some simple code to do mouse
    rotation. If anyone wishes to compare performance with the original
    version, it can downloaded from:
    http://cvs.freedesktop.org/mesa/Mesa/progs/demos/gears.c

    Arto Bendiken

    ------=_Part_967_24960416.1114927351434
    Content-Type: text/plain; name=gears.rb; charset=us-ascii
    Content-Transfer-Encoding: 7bit
    Content-Disposition: attachment; filename="gears.rb"

    #!/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




    ------=_Part_967_24960416.1114927351434--
    Arto Bendiken, May 1, 2005
    #1
    1. Advertising

  2. su, 2005-05-01 kello 09:02, Arto Bendiken kirjoitti:
    > Hi there,


    > The results were a bit surprising, in a good way. On my Linux computer
    > with ATI Radeon 9700, the compiled version of the original gears.c
    > gives an average 1271 FPS, whereas the gears.rb version achieves an
    > average of 1203 FPS. (Both programs were run for a couple of minutes
    > and results averaged.) That's only a 6% gap in performance.


    Hello,
    The reason the Ruby version runs fast is because the gears-program uses
    display lists and hence the draw function is pretty much limited just by
    the speed of OpenGL's display list drawing efficiency.

    I did a couple of experiments comparing drawing the gears with and
    without display lists and got the following results:

    C, using display lists: 3321FPS
    Ruby, using display lists: 3033FPS

    C, without display lists: 2417FPS
    Ruby, without display lists: 90FPS

    The 27x speed difference sounds about right..

    The gear function could probably be optimized by minimizing the amount
    of Ruby math there and doing as much as possible with GL.Translate and
    GL.Rotate, since the GPU does those (and fast!).

    It's definitely possible to use Ruby for writing GL apps, though good
    performance requires doing things in a way that uses GL's
    performance-helping things as much as possible (HW T&L, vertex arrays,
    display lists, or even writing inner loops and math-intensive parts in
    C).


    Anxiously waiting for the fast VM(s), :)
    Ilmari
    Ilmari Heikkinen, May 2, 2005
    #2
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. F. GEIGER
    Replies:
    9
    Views:
    1,199
    F. GEIGER
    May 3, 2004
  2. Andy Leszczynski

    wxPython demo /Process does not open new demo

    Andy Leszczynski, Feb 18, 2005, in forum: Python
    Replies:
    1
    Views:
    633
    Andy Leszczynski
    Feb 18, 2005
  3. reetesh nigam

    how tirbo gears work?

    reetesh nigam, Apr 16, 2008, in forum: Python
    Replies:
    0
    Views:
    306
    reetesh nigam
    Apr 16, 2008
  4. Michael Brooks
    Replies:
    10
    Views:
    427
    David Masover
    Mar 13, 2009
  5. Roberto Cm

    opengl problem (from gears sample)

    Roberto Cm, Jun 7, 2010, in forum: Ruby
    Replies:
    3
    Views:
    228
    Roberto Cm
    Jun 7, 2010
Loading...

Share This Page