Import gears_using_shaders into git
[gears_using_shaders.git] / gears_using_shaders.rb
blobcb10059dd1d80d7b5b0d58f09008e9f0286548e2
1 #!/usr/bin/env ruby
3 # 3-D gear wheels. This program is in the public domain.
5 # Command line options:
6 #    -info      print GL implementation information
7 #    -exit      automatically exit after 30 seconds
9 # 2009-Mar-08 Modified Ruby version by Michael Brooks changed to demonstrate 
10 #             using GLSL shaders to implement velvet and shiny materials.
11 # 2005-May-01 Ruby version by Arto Bendiken based on gears.c rev 1.8.
12 # 2005-Jan-09 Original C version (gears.c) by Brian Paul et al.
13 #             http://cvs.freedesktop.org/mesa/Mesa/progs/demos/gears.c?rev=1.8
15 require 'opengl'
16 require 'glut'
18 class Gears
20   include Math
22   # Define constants
23   LIGHT_POS = [100.0, 0.0, 0.0, 1.0]
24   RED = [0.8, 0.1, 0.0, 1.0]
25   GREEN = [0.0, 0.8, 0.2, 1.0]
26   BLUE = [0.2, 0.2, 1.0, 1.0]
28   def create_gear(inner_radius, outer_radius, width, teeth, tooth_depth)
29     # Draw a gear wheel based on the following inputs:
30     #
31     #   inner_radius - radius of hole at center
32     #   outer_radius - radius at center of teeth
33     #   width - width of gear
34     #   teeth - number of teeth
35     #   tooth_depth - depth of tooth
37     r0 = inner_radius
38     r1 = outer_radius - tooth_depth / 2.0
39     r2 = outer_radius + tooth_depth / 2.0
41     da = 2.0 * PI / teeth / 4.0
43     GL.Normal(0.0, 0.0, 1.0)
45     # Draw front face
46     GL.Begin(GL::QUAD_STRIP)
47       for i in 0..teeth
48         angle = i * 2.0 * PI / teeth
49         GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
50         GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
51         if i < teeth
52           GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
53           GL.Vertex3f(r1 * cos(angle + 3 * da),
54             r1 * sin(angle + 3 * da), width * 0.5)
55         end
56       end
57     GL.End()
58   
59     # Draw front sides of teeth
60     GL.Begin(GL::QUADS)
61       for i in 0...teeth
62         angle = i * 2.0 * PI / teeth
63         GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
64         GL.Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5)
65         GL.Vertex3f(r2 * cos(angle + 2 * da),
66           r2 * sin(angle + 2 * da), width * 0.5)
67         GL.Vertex3f(r1 * cos(angle + 3 * da),
68           r1 * sin(angle + 3 * da), width * 0.5)
69       end
70     GL.End()
71   
72     GL.Normal(0.0, 0.0, -1.0)
73   
74     # Draw back face
75     GL.Begin(GL::QUAD_STRIP)
76       for i in 0..teeth
77         angle = i * 2.0 * PI / teeth
78         GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
79         GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
80         if i < teeth
81           GL.Vertex3f(r1 * cos(angle + 3 * da),
82             r1 * sin(angle + 3 * da), -width * 0.5)
83           GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
84         end
85       end
86     GL.End()
87   
88     # Draw back sides of teeth
89     GL.Begin(GL::QUADS)
90       for i in 0...teeth
91         angle = i * 2.0 * PI / teeth
92         GL.Vertex3f(r1 * cos(angle + 3 * da),
93           r1 * sin(angle + 3 * da), -width * 0.5)
94         GL.Vertex3f(r2 * cos(angle + 2 * da),
95           r2 * sin(angle + 2 * da), -width * 0.5)
96         GL.Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5)
97         GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
98       end
99     GL.End()
100   
101     # Draw outward faces of teeth
102     GL.Begin(GL::QUAD_STRIP)
103       for i in 0...teeth
104         angle = i * 2.0 * PI / teeth
105         GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
106         GL.Vertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
107         u = r2 * cos(angle + da) - r1 * cos(angle)
108         v = r2 * sin(angle + da) - r1 * sin(angle)
109         len = sqrt(u * u + v * v)
110         u /= len
111         v /= len
112         GL.Normal(v, -u, 0.0)
113         GL.Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5)
114         GL.Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5)
115         GL.Normal(cos(angle), sin(angle), 0.0)
116         GL.Vertex3f(r2 * cos(angle + 2 * da),
117           r2 * sin(angle + 2 * da), width * 0.5)
118         GL.Vertex3f(r2 * cos(angle + 2 * da),
119           r2 * sin(angle + 2 * da), -width * 0.5)
120         u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da)
121         v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da)
122         GL.Normal(v, -u, 0.0)
123         GL.Vertex3f(r1 * cos(angle + 3 * da),
124           r1 * sin(angle + 3 * da), width * 0.5)
125         GL.Vertex3f(r1 * cos(angle + 3 * da),
126           r1 * sin(angle + 3 * da), -width * 0.5)
127         GL.Normal(cos(angle), sin(angle), 0.0)
128       end
129       GL.Vertex3f(r1 * cos(0), r1 * sin(0), width * 0.5)
130       GL.Vertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5)
131     GL.End()
132   
133     # Draw inside radius cylinder
134     GL.Begin(GL::QUAD_STRIP)
135       for i in 0..teeth
136         angle = i * 2.0 * PI / teeth
137         GL.Normal(-cos(angle), -sin(angle), 0.0)
138         GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
139         GL.Vertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
140       end
141     GL.End()
142   end
144   def create_shader_program(shader_base_file_name)
145     # Create GLSL program and shader objects
146     glsl_program = GL.CreateProgram()
147     glsl_vertex_shader = GL.CreateShader(GL_VERTEX_SHADER)
148     glsl_fragment_shader = GL.CreateShader(GL_FRAGMENT_SHADER)
150     # Load shader source into the shaders
151     GL.ShaderSource(glsl_vertex_shader,
152                     File.read(File.join(Dir.getwd, 
153                                         "#{shader_base_file_name}.vsh")))
154     GL.ShaderSource(glsl_fragment_shader, 
155                     File.read(File.join(Dir.getwd, 
156                                         "#{shader_base_file_name}.fsh")))
158     # Compile shaders
159     GL.CompileShader(glsl_vertex_shader)
160     GL.CompileShader(glsl_fragment_shader)
162     # Attach the shaders to the program
163     GL.AttachShader(glsl_program, glsl_vertex_shader)
164     GL.AttachShader(glsl_program, glsl_fragment_shader)
166     # Link the program
167     GL.LinkProgram(glsl_program)
168     
169     # Cleanup the shaders, the program copied them so they aren't needed anymore
170     GL.DeleteShader(glsl_vertex_shader)
171     GL.DeleteShader(glsl_fragment_shader)
172     
173     # Pass the GLSL program back to the caller
174     return(glsl_program)
175   end
176   
177   def setup_scene()
178     # Setup rotation tracking variables
179     @angle = 0.0
180     @view_rotx, @view_roty, @view_rotz = 20.0, 30.0, 0.0
182     # Setup OpenGL environment
183     GL.Enable(GL::DEPTH_TEST)
184     GL.Enable(GL::CULL_FACE)
185     GL.Lightfv(GL::LIGHT0, GL::POSITION, LIGHT_POS)
186     GL.Enable(GL::LIGHT0)
187     GL.ShadeModel(GL::SMOOTH)
188     GL.Enable(GL::NORMALIZE)
190     # Create the gear objects 
191     # Note: Includes some material attributes which the GLSL shaders 
192     #       can use or ignore at their disgression.
193     @gear1 = GL.GenLists(1)
194       GL.NewList(@gear1, GL::COMPILE)
195       GL.Material(GL::FRONT, GL::AMBIENT_AND_DIFFUSE, RED)
196       GL.Material(GL::FRONT, GL::SPECULAR, [1.0, 1.0, 1.0]);
197       GL.Material(GL::FRONT, GL::SHININESS, 50);
198       create_gear(1.0, 4.0, 1.0, 20, 0.7)
199     GL.EndList()
201     @gear2 = GL.GenLists(1)
202       GL.NewList(@gear2, GL::COMPILE)
203       GL.Material(GL::FRONT, GL::AMBIENT_AND_DIFFUSE, GREEN)
204       GL.Material(GL::FRONT, GL::SPECULAR, [1.0, 1.0, 1.0]);
205       GL.Material(GL::FRONT, GL::SHININESS, 50);
206       create_gear(0.5, 2.0, 2.0, 10, 0.7)
207     GL.EndList()
209     @gear3 = GL.GenLists(1)
210       GL.NewList(@gear3, GL::COMPILE)
211       GL.Material(GL::FRONT, GL::AMBIENT_AND_DIFFUSE, BLUE)
212       GL.Material(GL::FRONT, GL::SPECULAR, [1.0, 1.0, 1.0]);
213       GL.Material(GL::FRONT, GL::SHININESS, 50);
214       create_gear(1.3, 2.0, 0.5, 10, 0.7)
215     GL.EndList()
217     # Create the shader programs
218     # Note: These shaders are based on code provided by others which has been
219     #       slightly cleaned up or modified to better meet the needs of this
220     #       demonstration program.
221     @glsl_program1 = create_shader_program('shiny')
222     @glsl_program2 = create_shader_program('velvet')
223   end
225   def cleanup_scene()
226     # Delete OpenGL objects that Ruby doesn't know to clean up for us.
227     # Note: This doesn't get called yet.  See note in Gears.finalize method.
228     GL.DeleteProgram(@glsl_program1)
229     GL.DeleteProgram(@glsl_program2)
230     GL.DeleteLists(@gear1, 1)
231     GL.DeleteLists(@gear2, 1)
232     GL.DeleteLists(@gear3, 1)
233   end
234   
235   def draw_scene()
236     # Clear scene
237     GL.Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT);
239     # Save current scene rotation
240     GL.PushMatrix()
241     
242     # Rotate scene
243     GL.Rotate(@view_rotx, 1.0, 0.0, 0.0)
244     GL.Rotate(@view_roty, 0.0, 1.0, 0.0)
245     GL.Rotate(@view_rotz, 0.0, 0.0, 1.0)
247     # Activate shader program 1
248     GL.UseProgram(@glsl_program1)
250     # Draw gear 1
251     GL.PushMatrix()
252     GL.Translate(-3.0, -2.0, 0.0)
253     GL.Rotate(@angle, 0.0, 0.0, 1.0)
254     GL.CallList(@gear1)
255     GL.PopMatrix()
257     # Activate shader program 2
258     GL.UseProgram(@glsl_program2)
260     # Draw gear 2
261     GL.PushMatrix()
262     GL.Translate(3.1, -2.0, 0.0)
263     GL.Rotate(-2.0 * @angle - 9.0, 0.0, 0.0, 1.0)
264     GL.CallList(@gear2)
265     GL.PopMatrix()
267     # Draw gear 3
268     GL.PushMatrix()
269     GL.Translate(-3.1, 4.2, 0.0)
270     GL.Rotate(-2.0 * @angle - 25.0, 0.0, 0.0, 1.0)
271     GL.CallList(@gear3)
272     GL.PopMatrix()
274     # Restore "saved" scene rotation
275     GL.PopMatrix()
277     # Make the new scene visible in the window
278     GLUT.SwapBuffers()
280     # Calculate the drawing performance and display it every 5 seconds
281     @frames = 0 if not defined?(@frames)
282     @t0 = 0 if not defined?(@t0)
283     @frames += 1
284     t = GLUT.Get(GLUT::ELAPSED_TIME)
285     if t - @t0 >= 5000
286       seconds = (t - @t0) / 1000.0
287       fps = @frames / seconds
288       printf("%d frames in %6.3f seconds = %6.3f FPS\n", @frames, seconds, fps)
289       @t0, @frames = t, 0
290       exit if defined?(@autoexit) and t >= 999.0 * @autoexit
291     end
292   end
294   def handle_window_resize(width, height)
295     # Handle new window size or exposure
296     h = height.to_f / width.to_f
297     GL.Viewport(0, 0, width, height)
298     GL.MatrixMode(GL::PROJECTION)
299     GL.LoadIdentity()
300     GL.Frustum(-1.0, 1.0, -h, h, 5.0, 60.0)
301     GL.MatrixMode(GL::MODELVIEW)
302     GL.LoadIdentity()
303     GL.Translate(0.0, 0.0, -40.0)
304   end
306   def handle_window_visibilitychange(visibility)
307     # Handle changes in the visibility of the window
308     GLUT.IdleFunc((visibility == GLUT::VISIBLE ? method(:handle_idling).to_proc : nil))
309   end
311   def handle_keypress(k, x, y)
312     # Handle keyboard requests for Z view angle change or exit 
313     case k
314       when ?z # pressed 'z', inc Z rotation
315         @view_rotz += 5.0
316       when ?Z # pressed 'z', dec Z rotation
317         @view_rotz -= 5.0
318       when 27 # pressed escape, exit
319         exit
320     end
321     GLUT.PostRedisplay()
322   end
324   def handle_special_keypress(k, x, y)
325     # Handle keyboard requests for X and Y view angle change 
326     case k
327       when GLUT::KEY_UP # pressed up-arrow, inc X rotation
328         @view_rotx += 5.0
329       when GLUT::KEY_DOWN # pressed down-arrow, dec X rotation
330         @view_rotx -= 5.0
331       when GLUT::KEY_LEFT # pressed left-arrow, inc Y rotation
332         @view_roty += 5.0
333       when GLUT::KEY_RIGHT # pressed right-arrow, dec Y rotation
334         @view_roty -= 5.0
335     end
336     GLUT.PostRedisplay()
337   end
339   def handle_mouse_click(button, state, x, y)
340     # Record mouse position when buttons are first clicked to support rotation calc. 
341     @mouse = state
342     @x0, @y0 = x, y
343   end
344   
345   def handle_mousedown_motion(x, y)
346     # Handle mouse requests for X and Y view angle changes. 
347     if @mouse == GLUT::DOWN
348       @view_roty -= @x0 - x
349       @view_rotx -= @y0 - y
350     end
351     @x0, @y0 = x, y
352   end
354   def handle_idling()
355     # Handle additonal tasks in between GLUT drawing events...
356     
357     # - Record the current GLUT ticks for later use in perofmance calc 
358     t = GLUT.Get(GLUT::ELAPSED_TIME) / 1000.0
359     @t0_idle = t if !defined?(@t0_idle)
360     
361     # - Tell the gears to rotate 90 degrees per second
362     @angle += 70.0 * (t - @t0_idle)
363     @t0_idle = t
364     GLUT.PostRedisplay()
365   end
367   def handle_command_line_parameters()
368     # Handle the following command line parameters:
369     # 
370     #   -info : Prints OpenGL driver information.
371     #   -exit : Forces app to run for 30 seconds then exit. 
372     
373     ARGV.each do |arg|
374       case arg
375         when '-info'
376           printf("GL_RENDERER   = %s\n", GL.GetString(GL::RENDERER))
377           printf("GL_VERSION    = %s\n", GL.GetString(GL::VERSION))
378           printf("GL_VENDOR     = %s\n", GL.GetString(GL::VENDOR))
379           printf("GL_EXTENSIONS = %s\n", GL.GetString(GL::EXTENSIONS))
380         when '-exit'
381           @autoexit = 30
382           printf("Auto Exit after %i seconds.\n", @autoexit);
383       end
384     end
385   end
386   
387   def initialize()
388     # Exit if the environment can't handle the OpenGL 2.0 style 
389     # GLSL shader api commands
390     if not (GL.respond_to?('CreateProgram') and
391             GL.respond_to?('CreateShader') and
392             GL.respond_to?('ShaderSource') and
393             GL.respond_to?('CompileShader') and
394             GL.respond_to?('AttachShader') and
395             GL.respond_to?('LinkProgram') and
396             GL.respond_to?('DeleteShader') and
397             GL.respond_to?('DeleteProgram'))
398       puts('Error - The program has terminated because the environment does ' +
399            'not support OpenGL 2.0 or greater GLSL shaders.')
400       exit
401     end
403     # Setup OpenGL environment
404     GLUT.Init()
405     GLUT.InitDisplayMode(GLUT::RGBA | GLUT::DEPTH | GLUT::DOUBLE)
406     GLUT.InitWindowPosition(0, 0)
407     GLUT.InitWindowSize(300, 300)
408     GLUT.CreateWindow('Gears')
410     # Handle command line parameters
411     handle_command_line_parameters()
412     
413     # Setup scene
414     setup_scene()
416     # Setup GLUT event handlers
417     GLUT.DisplayFunc(method(:draw_scene).to_proc)
418     GLUT.ReshapeFunc(method(:handle_window_resize).to_proc)
419     GLUT.VisibilityFunc(method(:handle_window_visibilitychange).to_proc)
420     GLUT.KeyboardFunc(method(:handle_keypress).to_proc)
421     GLUT.SpecialFunc(method(:handle_special_keypress).to_proc)
422     GLUT.MouseFunc(method(:handle_mouse_click).to_proc)
423     GLUT.MotionFunc(method(:handle_mousedown_motion).to_proc)
424   end
426   def Gears.finalize()
427     # Cleanup the OpenGL objects
428     # Note: Haven't found a way to get this finalize method called in a  
429     #       standard GLUT environment when closing the applications.
430     cleanup_scene()
431   end
432   
433   def start()
434     # Pass control to GLUT to display and manage the scene
435     GLUT.MainLoop()
436   end
440 # *** Program execution starts here ***
441 Gears.new().start()