Skip to content
Snippets Groups Projects
gltest.rb 9.84 KiB
Newer Older
  • Learn to ignore specific revisions
  • #!/usr/bin/env ruby
    
    require 'fox16'
    require 'fox16/kwargs'
    begin
      require 'opengl'
    rescue LoadError
      require 'fox16/missingdep'
      MSG = <<EOM
      Sorry, this example depends on the OpenGL extension. Please
      check the Ruby Application Archives for an appropriate
      download site.
    EOM
      missingDependency(MSG)
    end
    
    
    include Fox
    
    class GLTestWindow < FXMainWindow
    
      # How often our timer will fire (in milliseconds)
      TIMER_INTERVAL = 100
    
      # Rotate the boxes when a timer message is received
      def onTimeout(sender, sel, ptr)
        @angle += 2.0
        if @angle > 360.0
          @angle -= 360.0
        end
        drawScene()
        @timer = getApp().addTimeout(TIMER_INTERVAL, method(:onTimeout))
      end
    
      # Rotate the boxes when a chore message is received
      def onChore(sender, sel, ptr)
        @angle += 2.0
        if @angle > 360.0
          @angle -= 360.0
        end
        drawScene()
        @chore = getApp().addChore(method(:onChore))
      end
    
      # Draws a simple box using the given corners
      def drawBox(xmin, ymin, zmin, xmax, ymax, zmax)
        GL.Begin(GL::TRIANGLE_STRIP)
          GL.Normal(0.0, 0.0, -1.0)
          GL.Vertex(xmin, ymin, zmin)
          GL.Vertex(xmin, ymax, zmin)
          GL.Vertex(xmax, ymin, zmin)
          GL.Vertex(xmax, ymax, zmin)
        GL.End()
      
        GL.Begin(GL::TRIANGLE_STRIP)
          GL.Normal(1.0, 0.0, 0.0)
          GL.Vertex(xmax, ymin, zmin)
          GL.Vertex(xmax, ymax, zmin)
          GL.Vertex(xmax, ymin, zmax)
          GL.Vertex(xmax, ymax, zmax)
        GL.End()
      
        GL.Begin(GL::TRIANGLE_STRIP)
          GL.Normal(0.0, 0.0, 1.0)
          GL.Vertex(xmax, ymin, zmax)
          GL.Vertex(xmax, ymax, zmax)
          GL.Vertex(xmin, ymin, zmax)
          GL.Vertex(xmin, ymax, zmax)
        GL.End()
      
        GL.Begin(GL::TRIANGLE_STRIP)
          GL.Normal(-1.0, 0.0, 0.0)
          GL.Vertex(xmin, ymin, zmax)
          GL.Vertex(xmin, ymax, zmax)
          GL.Vertex(xmin, ymin, zmin)
          GL.Vertex(xmin, ymax, zmin)
        GL.End()
      
        GL.Begin(GL::TRIANGLE_STRIP)
          GL.Normal(0.0, 1.0, 0.0)
          GL.Vertex(xmin, ymax, zmin)
          GL.Vertex(xmin, ymax, zmax)
          GL.Vertex(xmax, ymax, zmin)
          GL.Vertex(xmax, ymax, zmax)
        GL.End()
      
        GL.Begin(GL::TRIANGLE_STRIP)
          GL.Normal(0.0, -1.0, 0.0)
          GL.Vertex(xmax, ymin, zmax)
          GL.Vertex(xmax, ymin, zmin)
          GL.Vertex(xmin, ymin, zmax)
          GL.Vertex(xmin, ymin, zmin)
        GL.End()
      end
    
      # Draw the GL scene
      def drawScene
        lightPosition = [15.0, 10.0, 5.0, 1.0]
        lightAmbient  = [ 0.1,  0.1, 0.1, 1.0]
        lightDiffuse  = [ 0.9,  0.9, 0.9, 1.0]
        redMaterial   = [ 1.0,  0.0, 0.0, 1.0]
        blueMaterial  = [ 0.0,  0.0, 1.0, 1.0]
      
        width = @glcanvas.width.to_f
        height = @glcanvas.height.to_f
        aspect = width/height
      
        # Make context current
        @glcanvas.makeCurrent()
        
        GL.Viewport(0, 0, @glcanvas.width, @glcanvas.height)
      
        GL.ClearColor(1.0, 1.0, 1.0, 1.0)
        GL.Clear(GL::COLOR_BUFFER_BIT|GL::DEPTH_BUFFER_BIT)
        GL.Enable(GL::DEPTH_TEST)
        
        GL.Disable(GL::DITHER)
      
        GL.MatrixMode(GL::PROJECTION)
        GL.LoadIdentity()
        GLU.Perspective(30.0, aspect, 1.0, 100.0)
      
        GL.MatrixMode(GL::MODELVIEW)
        GL.LoadIdentity()
        GLU.LookAt(5.0, 10.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
      
        GL.ShadeModel(GL::SMOOTH)
        GL.Light(GL::LIGHT0, GL::POSITION, lightPosition)
        GL.Light(GL::LIGHT0, GL::AMBIENT, lightAmbient)
        GL.Light(GL::LIGHT0, GL::DIFFUSE, lightDiffuse)
        GL.Enable(GL::LIGHT0)
        GL.Enable(GL::LIGHTING)
      
        GL.Material(GL::FRONT, GL::AMBIENT, blueMaterial)
        GL.Material(GL::FRONT, GL::DIFFUSE, blueMaterial)
      
        GL.PushMatrix()
        GL.Rotated(@angle, 0.0, 1.0, 0.0)
        drawBox(-1, -1, -1, 1, 1, 1)
      
        GL.Material(GL::FRONT, GL::AMBIENT, redMaterial)
        GL.Material(GL::FRONT, GL::DIFFUSE, redMaterial)
      
        GL.PushMatrix()
        GL.Translated(0.0, 1.75, 0.0)
        GL.Rotated(@angle, 0.0, 1.0, 0.0)
        drawBox(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5)
        GL.PopMatrix()
      
        GL.PushMatrix()
        GL.Translated(0.0, -1.75, 0.0)
        GL.Rotated(@angle, 0.0, 1.0, 0.0)
        drawBox(-0.5, -0.5, -0.5, 0.5, 0.5, 0.5)
        GL.PopMatrix()
      
        GL.PushMatrix()
        GL.Rotated(90.0, 1.0, 0.0, 0.0)
        GL.Translated(0.0, 1.75, 0.0)
        GL.Rotated(@angle, 0.0, 1.0, 0.0)
        drawBox(-0.5,-0.5,-0.5,0.5,0.5,0.5)
        GL.PopMatrix()
      
        GL.PushMatrix()
        GL.Rotated(90.0, -1.0, 0.0, 0.0)
        GL.Translated(0.0,1.75,0.0)
        GL.Rotated(@angle, 0.0, 1.0, 0.0)
        drawBox(-0.5,-0.5,-0.5,0.5,0.5,0.5)
        GL.PopMatrix()
      
        GL.PushMatrix()
        GL.Rotated(90.0, 0.0, 0.0, 1.0)
        GL.Translated(0.0,1.75,0.0)
        GL.Rotated(@angle, 0.0, 1.0, 0.0)
        drawBox(-0.5,-0.5,-0.5,0.5,0.5,0.5)
        GL.PopMatrix()
      
        GL.PushMatrix()
        GL.Rotated(90.0, 0.0, 0.0, -1.0)
        GL.Translated(0.0,1.75,0.0)
        GL.Rotated(@angle, 0.0, 1.0, 0.0)
        drawBox(-0.5,-0.5,-0.5,0.5,0.5,0.5)
        GL.PopMatrix()
      
        GL.PopMatrix()
      
        # Swap if it is double-buffered
        if @glvisual.isDoubleBuffer
          @glcanvas.swapBuffers
        end
        
        # Make context non-current
        @glcanvas.makeNonCurrent
      end
    
      def initialize(app)
        # Invoke the base class initializer
        super(app, "OpenGL Test Application", :opts => DECOR_ALL, :width => 800, :height => 600)
    
        # Construct the main window elements
        frame = FXHorizontalFrame.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y)
        frame.padLeft, frame.padRight = 0, 0
        frame.padTop, frame.padBottom = 0, 0
    
        # Left pane to contain the glcanvas
        glcanvasFrame = FXVerticalFrame.new(frame,
          LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT)
        glcanvasFrame.padLeft, glcanvasFrame.padRight = 10, 10
        glcanvasFrame.padTop, glcanvasFrame.padBottom = 10, 10
      
        # Label above the glcanvas
        FXLabel.new(glcanvasFrame, "OpenGL Canvas Frame", nil,
          JUSTIFY_CENTER_X|LAYOUT_FILL_X)
      
        # Horizontal divider line
        FXHorizontalSeparator.new(glcanvasFrame, SEPARATOR_GROOVE|LAYOUT_FILL_X)
      
        # Drawing glcanvas
        glpanel = FXVerticalFrame.new(glcanvasFrame, (FRAME_SUNKEN|FRAME_THICK|
          LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT))
        glpanel.padLeft, glpanel.padRight = 0, 0
        glpanel.padTop, glpanel.padBottom = 0, 0
          
        # A visual to draw OpenGL
        @glvisual = FXGLVisual.new(getApp(), VISUAL_DOUBLEBUFFER)
      
        # Drawing glcanvas
        @glcanvas = FXGLCanvas.new(glpanel, @glvisual, :opts => LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT)
        @glcanvas.connect(SEL_PAINT) { drawScene }
        @glcanvas.connect(SEL_CONFIGURE) do
          if @glcanvas.makeCurrent
            GL.Viewport(0, 0, @glcanvas.width, @glcanvas.height)
            @glcanvas.makeNonCurrent
          end
        end
      
        # Right pane for the buttons
        buttonFrame = FXVerticalFrame.new(frame, LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT)
        buttonFrame.padLeft, buttonFrame.padRight = 10, 10
        buttonFrame.padTop, buttonFrame.padBottom = 10, 10
          
        # Label above the buttons
        FXLabel.new(buttonFrame, "Button Frame", nil,
          JUSTIFY_CENTER_X|LAYOUT_FILL_X)
      
        # Horizontal divider line
        FXHorizontalSeparator.new(buttonFrame, SEPARATOR_RIDGE|LAYOUT_FILL_X)
      
        # Spin according to timer
        spinTimerBtn = FXButton.new(buttonFrame, "Spin &Timer\tSpin using interval timers\nNote the app blocks until the interal has elapsed...",nil,nil,0,FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT)
        spinTimerBtn.padLeft, spinTimerBtn.padRight = 10, 10
        spinTimerBtn.padTop, spinTimerBtn.padBottom = 5, 5
        spinTimerBtn.connect(SEL_COMMAND) do
          @spinning = true
          @timer = getApp().addTimeout(TIMER_INTERVAL, method(:onTimeout))
        end
        spinTimerBtn.connect(SEL_UPDATE) do |sender, sel, ptr|
          @spinning ? sender.disable : sender.enable
        end
    
        # Spin according to chore
        spinChoreBtn = FXButton.new(buttonFrame,
          "Spin &Chore\tSpin as fast as possible using chores\nNote even though the
          app is very responsive, it never blocks;\nthere is always something to
          do...", :opts => FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT)
        spinChoreBtn.padLeft, spinChoreBtn.padRight = 10, 10
        spinChoreBtn.padTop, spinChoreBtn.padBottom = 5, 5
        spinChoreBtn.connect(SEL_COMMAND) do
          @spinning = true
          @chore = getApp().addChore(method(:onChore))
        end
        spinChoreBtn.connect(SEL_UPDATE) do |sender, sel, ptr|
          @spinning ? sender.disable : sender.enable
        end
      
        # Stop spinning
        stopBtn = FXButton.new(buttonFrame,
          "&Stop Spin\tStop this mad spinning, I'm getting dizzy",
          :opts => FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT)
        stopBtn.padLeft, stopBtn.padRight = 10, 10
        stopBtn.padTop, stopBtn.padBottom = 5, 5
        stopBtn.connect(SEL_COMMAND) do
          @spinning = false
          if @timer
            getApp().removeTimeout(@timer)
            @timer = nil
          end
          if @chore
            getApp().removeChore(@chore)
            @chore = nil
          end
        end
        stopBtn.connect(SEL_UPDATE) do |sender, sel, ptr|
          @spinning ? sender.enable : sender.disable
        end
      
        # Exit button
        exitBtn = FXButton.new(buttonFrame, "&Exit\tExit the application", nil,
          getApp(), FXApp::ID_QUIT,
          FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT)
        exitBtn.padLeft, exitBtn.padRight = 10, 10
        exitBtn.padTop, exitBtn.padBottom = 5, 5
          
        # Make a tooltip
        FXToolTip.new(getApp())
      
        # Initialize private variables
        @spinning = false
        @chore = nil
        @timer = nil
        @angle = 0.0
      end
    
      # Create and initialize
      def create
        super
        show(PLACEMENT_SCREEN)
      end
    end
    
    if __FILE__ == $0
      # Construct the application
      application = FXApp.new("GLTest", "FoxTest")
      
      # To ensure that the chores-based spin will run as fast as possible,
      # we can disable the chore in FXRuby's event loop that tries to schedule
      # other threads. This is OK for this program because there aren't any
      # other Ruby threads running.
      
      application.disableThreads
    
      # Construct the main window
      GLTestWindow.new(application)
    
      # Create the app's windows
      application.create
    
      # Run the application
      application.run
    end