From: Arto Bendiken Date: 2005-05-01T15:02:52+09:00 Subject: OpenGL 3D gears demo for Ruby ------=_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--