VisionEgg.SphereMap

Package VisionEgg :: Module SphereMap |
[frames] | no frames]

# Source Code for Module VisionEgg.SphereMap

       1  # The Vision Egg: SphereMap
       2  #
       3  # Copyright (C) 2001-2004 Andrew Straw.
       4  # Copyright (C) 2005-2008 California Institute of Technology
       5  #
       6  # Author: Andrew Straw <astraw@users.sourceforge.net>
       7  # URL: <http://www.visionegg.org/>
       8  #
       9  # Distributed under the terms of the GNU Lesser General Public License
      10  # (LGPL). See LICENSE.TXT that came with this file.
      11
      12  """
      13  Stimuli on spheres, including texture maps.
      14
      15  """
      16
      17  import math, types
      18
      19  import logging
      20
      21  import VisionEgg.Core
      22  import VisionEgg.Textures
      23  import VisionEgg.Text
      24  import VisionEgg.Gratings
      25  import VisionEgg.ThreeDeeMath
      26  import VisionEgg.ParameterTypes as ve_types
      27
      28  import numpy
      29  import numpy.oldnumeric as Numeric
      30  import Image
      31
      32  import VisionEgg.GL as gl # get all OpenGL stuff in one namespace
      33
      34  __version__ = VisionEgg.release_name
      35
      36 -class AzElGrid(VisionEgg.Core.Stimulus):
      37      """Spherical grid of iso-azimuth and iso-elevation lines.
      38
      39      Parameters
      40      ==========
      41      anti_aliasing    -- (Boolean)
      42                          Default: True
      43      center_azimuth   -- (Real)
      44                          Default: 0.0
      45      center_elevation -- (Real)
      46                          Default: 0.0
      47      major_line_color -- (AnyOf(Sequence3 of Real or Sequence4 of Real))
      48                          Default: (0.0, 0.0, 0.0)
      49      major_line_width -- (Real)
      50                          Default: 2.0
      51      minor_line_color -- (AnyOf(Sequence3 of Real or Sequence4 of Real))
      52                          Default: (0.0, 0.0, 1.0)
      53      minor_line_width -- (Real)
      54                          Default: 1.0
      55      my_viewport      -- (Instance of <class 'VisionEgg.Core.Viewport'>)
      56                          Default: (determined at runtime)
      57      on               -- (Boolean)
      58                          Default: True
      59      text_offset      -- (Sequence2 of Real)
      60                          Default: (3, -2)
      61
      62      Constant Parameters
      63      ===================
      64      az_major_spacing       -- (Real)
      65                                Default: 30.0
      66      az_minor_spacing       -- (Real)
      67                                Default: 10.0
      68      el_major_spacing       -- (Real)
      69                                Default: 30.0
      70      el_minor_spacing       -- (Real)
      71                                Default: 10.0
      72      font_size              -- (UnsignedInteger)
      73                                Default: 24
      74      num_samples_per_circle -- (UnsignedInteger)
      75                                Default: 100
      76      radius                 -- (Real)
      77                                Default: 1.0
      78      text_anchor            -- (String)
      79                                Default: lowerleft
      80      text_color             -- (AnyOf(Sequence3 of Real or Sequence4 of Real))
      81                                Default: (0.0, 0.0, 0.0)
      82      use_text               -- (Boolean)
      83                                Default: True
      84      """
      85
      86      parameters_and_defaults = {
      87          'on':(True,
      88                ve_types.Boolean),
      89          'center_azimuth':(0.0, # 0=right, 90=right
      90                            ve_types.Real),
      91          'center_elevation':(0.0, # 0=right, 90=up
      92                              ve_types.Real),
      93          'minor_line_width':(1.0,
      94                              ve_types.Real),
      95          'major_line_width':(2.0,
      96                              ve_types.Real),
      97          'minor_line_color':((0.0,0.0,1.0),
      98                              ve_types.AnyOf(ve_types.Sequence3(ve_types.Real),
      99                                             ve_types.Sequence4(ve_types.Real))),
     100          'major_line_color':((0.0,0.0,0.0),
     101                              ve_types.AnyOf(ve_types.Sequence3(ve_types.Real),
     102                                             ve_types.Sequence4(ve_types.Real))),
     103          'my_viewport':(None, # viewport I'm in
     104                         ve_types.Instance(VisionEgg.Core.Viewport)),
     105          'text_offset':((3,-2), # offset (x,y) to nudge text labels
     106                         ve_types.Sequence2(ve_types.Real)),
     107          'anti_aliasing' : ( True,
     108                              ve_types.Boolean ),
     109          }
     110
     111      constant_parameters_and_defaults = {
     112          'use_text':(True,
     113                      ve_types.Boolean),
     114          'radius':(1.0,
     115                    ve_types.Real),
     116          'az_minor_spacing':(10.0,
     117                              ve_types.Real),
     118          'az_major_spacing':(30.0,
     119                              ve_types.Real),
     120          'el_minor_spacing':(10.0,
     121                              ve_types.Real),
     122          'el_major_spacing':(30.0,
     123                              ve_types.Real),
     124          'num_samples_per_circle':(100,
     125                                    ve_types.UnsignedInteger),
     126          'font_size':(24,
     127                       ve_types.UnsignedInteger),
     128          'text_color':((0.0,0.0,0.0),
     129                        ve_types.AnyOf(ve_types.Sequence3(ve_types.Real),
     130                                       ve_types.Sequence4(ve_types.Real))),
     131          'text_anchor':('lowerleft',
     132                         ve_types.String),
     133          }
     134
     135      __slots__ = (
     136          'cached_minor_lines_display_list',
     137          'cached_major_lines_display_list',
     138          'text_viewport',
     139          'text_viewport_orig',
     140          '_gave_alpha_warning',
     141          'labels',
     142          'labels_xyz',
     143          )
     144
     145 -    def __init__(self,**kw):
     146          VisionEgg.Core.Stimulus.__init__(self,**kw)
     147          self.cached_minor_lines_display_list = gl.glGenLists(1) # Allocate a new display list
     148          self.cached_major_lines_display_list = gl.glGenLists(1) # Allocate a new display list
     149          self.__rebuild_display_lists()
     150          self.text_viewport = None # not set yet
     151          self._gave_alpha_warning = False
     152
     153 -    def __rebuild_display_lists(self):
     154          def get_xyz(theta,phi,radius):
     155              # theta normally between 0 and pi (north pole to south pole)
     156              # phi between -pi and pi
     157              y = radius * math.cos( theta )
     158              w = radius * math.sin( theta )
     159              x = w * math.cos( phi )
     160              z = w * math.sin( phi )
     161              return x,y,z
     162          def draw_half_great_circle(az):
     163              for i in range(cp.num_samples_per_circle/2):
     164                  # let theta exceed 1 pi to draw 2nd half of circle
     165                  theta_start = i/float(cp.num_samples_per_circle)*2*math.pi
     166                  theta_stop = (i+1)/float(cp.num_samples_per_circle)*2*math.pi
     167                  phi_start = phi_stop = (az-90.0)/180.0*math.pi
     168                  x_start,y_start,z_start = get_xyz(theta_start,phi_start,cp.radius)
     169                  x_stop,y_stop,z_stop = get_xyz(theta_stop,phi_stop,cp.radius)
     170                  gl.glVertex3f(x_start, y_start, z_start)
     171                  gl.glVertex3f(x_stop, y_stop, z_stop)
     172          def draw_iso_elevation_circle(el):
     173              # el from -90 = pi to el 90 = 0
     174              theta_start = theta_stop = -(el-90) / 180.0 * math.pi
     175              for i in range(cp.num_samples_per_circle):
     176                  phi_start = i/float(cp.num_samples_per_circle)*2*math.pi
     177                  phi_stop = (i+1)/float(cp.num_samples_per_circle)*2*math.pi
     178                  x_start,y_start,z_start = get_xyz(theta_start,phi_start,cp.radius)
     179                  x_stop,y_stop,z_stop = get_xyz(theta_stop,phi_stop,cp.radius)
     180                  gl.glVertex3f(x_start, y_start, z_start)
     181                  gl.glVertex3f(x_stop, y_stop, z_stop)
     182
     183          cp = self.constant_parameters
     184          # Weird range construction to be sure to include zero.
     185          azs_major = numpy.concatenate((
     186              numpy.arange(0.0,180.0,cp.az_major_spacing),
     187              -numpy.arange(0.0,180.0,cp.az_major_spacing)[1:]))
     188          azs_minor = numpy.concatenate((
     189              numpy.arange(0.0,180.0,cp.az_minor_spacing),
     190              -numpy.arange(0.0,180.0,cp.az_minor_spacing)[1:]))
     191          els_major = numpy.concatenate((
     192              numpy.arange(0.0,90.0,cp.el_major_spacing),
     193              -numpy.arange(0.0,90.0,cp.el_major_spacing)[1:]))
     194          els_minor = numpy.concatenate((
     195              numpy.arange(0.0,90.0,cp.el_minor_spacing),
     196              -numpy.arange(0.0,90.0,cp.el_minor_spacing)[1:]))
     197
     198          gl.glNewList(self.cached_minor_lines_display_list,gl.GL_COMPILE)
     199          gl.glBegin(gl.GL_LINES)
     200          # az minor
     201          for az in azs_minor:
     202              if az in azs_major:
     203                  continue # draw only once as major
     204              draw_half_great_circle(az)
     205          for el in els_minor:
     206              if el in els_major:
     207                  continue # draw only once as major
     208              draw_iso_elevation_circle(el)
     209          gl.glEnd()
     210          gl.glEndList()
     211
     212          gl.glNewList(self.cached_major_lines_display_list,gl.GL_COMPILE)
     213          gl.glBegin(gl.GL_LINES)
     214          for az in azs_major:
     215              draw_half_great_circle(az)
     216          for el in els_major:
     217              draw_iso_elevation_circle(el)
     218          gl.glEnd()
     219          gl.glEndList()
     220
     221          if cp.use_text:
     222              self.labels = []
     223              self.labels_xyz = []
     224              els_major = list(els_major)+[90.0] # make sure we have north pole
     225              for el in els_major:
     226                  for az in azs_major:
     227                      theta = -(el-90) / 180.0 * math.pi
     228                      phi = (az-90.0)/180.0*math.pi
     229                      x,y,z = get_xyz(theta,phi,cp.radius)
     230                      self.labels_xyz.append((x,y,z))
     231                      self.labels.append(
     232                          VisionEgg.Text.Text( text = '%.0f, %.0f'%(az,el),
     233                                               font_size = cp.font_size,
     234                                               color = cp.text_color,
     235                                               anchor = cp.text_anchor,
     236                                               )
     237                          )
     238                      if (el == -90) or (el == 90):
     239                          self.labels[-1].parameters.text = 'x, %.0f'%(el,)
     240                          break # only one label at the poles
     241
     242              self.labels_xyz = Numeric.array(self.labels_xyz)
     243
     244 -    def draw(self):
     245          p = self.parameters
     246          cp = self.constant_parameters
     247          if p.on:
     248              # Set OpenGL state variables
     249              gl.glDisable( gl.GL_DEPTH_TEST )
     250              gl.glDisable( gl.GL_TEXTURE_2D )  # Make sure textures are not drawn
     251              gl.glMatrixMode(gl.GL_MODELVIEW)
     252              gl.glPushMatrix()
     253              gl.glRotatef(p.center_azimuth,0.0,-1.0,0.0)
     254              gl.glRotatef(p.center_elevation,1.0,0.0,0.0)
     255
     256              if p.anti_aliasing:
     257                  if len(p.minor_line_color) == 4 and not self._gave_alpha_warning:
     258                      if p.minor_line_color[3] != 1.0:
     259                          logger = logging.getLogger('VisionEgg.SphereMap')
     260                          logger.warning("The parameter anti_aliasing is "
     261                                         "set to true in the AzElGrid "
     262                                         "stimulus class, but the color "
     263                                         "parameter specifies an alpha "
     264                                         "value other than 1.0.  To "
     265                                         "acheive the best anti-aliasing, "
     266                                         "ensure that the alpha value for "
     267                                         "the color parameter is 1.0.")
     268                          self._gave_alpha_warning = 1
     269                  if len(p.major_line_color) == 4 and not self._gave_alpha_warning:
     270                      if p.major_line_color[3] != 1.0:
     271                          logger = logging.getLogger('VisionEgg.SphereMap')
     272                          logger.warning("The parameter anti_aliasing is "
     273                                         "set to true in the AzElGrid "
     274                                         "stimulus class, but the color "
     275                                         "parameter specifies an alpha "
     276                                         "value other than 1.0.  To "
     277                                         "acheive the best anti-aliasing, "
     278                                         "ensure that the alpha value for "
     279                                         "the color parameter is 1.0.")
     280                          self._gave_alpha_warning = 1
     281                  gl.glEnable( gl.GL_LINE_SMOOTH )
     282                  # allow max_alpha value to control blending
     283                  gl.glEnable( gl.GL_BLEND )
     284                  gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA )
     285              else:
     286                  gl.glDisable( gl.GL_BLEND )
     287
     288              if len(p.minor_line_color)==3:
     289                  gl.glColor3f(*p.minor_line_color)
     290              elif len(p.minor_line_color)==4:
     291                  gl.glColor4f(*p.minor_line_color)
     292              gl.glLineWidth(p.minor_line_width)
     293              gl.glCallList(self.cached_minor_lines_display_list)
     294
     295              if len(p.major_line_color)==3:
     296                  gl.glColor3f(*p.major_line_color)
     297              elif len(p.major_line_color)==4:
     298                  gl.glColor4f(*p.major_line_color)
     299              gl.glLineWidth(p.major_line_width)
     300              gl.glCallList(self.cached_major_lines_display_list)
     301
     302              if p.anti_aliasing:
     303                  gl.glDisable( gl.GL_LINE_SMOOTH ) # turn off
     304
     305              if cp.use_text:
     306                  my_view = p.my_viewport
     307                  if (my_view is None) or (not my_view._is_drawing):
     308                      raise ValueError('use_text is True, but my_viewport not (properly) assigned')
     309
     310                  if self.text_viewport is None or self.text_viewport_orig != my_view:
     311                      # make viewport for text (uses default orthographic projection)
     312                      vp = my_view.parameters
     313                      self.text_viewport = VisionEgg.Core.Viewport(screen=vp.screen,
     314                                                                   position=vp.position,
     315                                                                   size=vp.size,
     316                                                                   anchor=vp.anchor,
     317                                                                   )
     318                      lowerleft = VisionEgg._get_lowerleft(vp.position,vp.anchor,vp.size)
     319                      self.text_viewport.parameters.projection.stateless_translate(-lowerleft[0],-lowerleft[1],0)
     320                      self.text_viewport_orig = p.my_viewport # in case my_viewport changes, change text_viewport
     321
     322                  # draw text labels
     323                  my_proj = my_view.parameters.projection
     324
     325                  xyz = self.labels_xyz
     326
     327                  t = VisionEgg.ThreeDeeMath.TransformMatrix()
     328                  t.rotate( p.center_azimuth,0.0,-1.0,0.0  ) # acheive same transforms as the lines
     329                  t.rotate( p.center_elevation,1.0,0.0,0.0 )
     330
     331                  xyz = t.transform_vertices(self.labels_xyz)
     332
     333                  clip = my_proj.eye_2_clip(xyz)
     334                  try:
     335                      # this is much faster when no OverflowError...
     336                      window_coords = my_view.clip_2_window(clip)
     337                      all_at_once = True
     338                  except OverflowError:
     339                      all_at_once = False
     340                  draw_labels = []
     341                  for i in range(len(self.labels)):
     342                      if clip[i,3] < 0: continue # this vertex is not on screen
     343                      label = self.labels[i]
     344                      if all_at_once:
     345                          this_pos = window_coords[i,:2]
     346                      else:
     347                          try:
     348                              window_coords = my_view.clip_2_window(clip[i,:])
     349                          except OverflowError:
     350                              continue # not much we can do with this vertex, either
     351                          this_pos = window_coords[:2]
     352                      label.parameters.position = (this_pos[0] + p.text_offset[0],
     353                                                   this_pos[1] + p.text_offset[1])
     354                      draw_labels.append(label)
     355                  self.text_viewport.parameters.stimuli = draw_labels
     356                  self.text_viewport.draw()
     357                  my_view.make_current() # restore viewport
     358              gl.glPopMatrix()
     359
     360 -class SphereMap(VisionEgg.Textures.TextureStimulusBaseClass):
     361      """Mercator mapping of rectangular texture onto sphere.
     362
     363      Parameters
     364      ==========
     365      center_azimuth     -- (Real)
     366                            Default: 0.0
     367      center_elevation   -- (Real)
     368                            Default: 0.0
     369      contrast           -- (Real)
     370                            Default: 1.0
     371      on                 -- (Boolean)
     372                            Default: True
     373      radius             -- (Real)
     374                            Default: 1.0
     375      slices             -- (UnsignedInteger)
     376                            Default: 30
     377      stacks             -- (UnsignedInteger)
     378                            Default: 30
     379      texture            -- source of texture data (Instance of <class 'VisionEgg.Textures.Texture'>)
     380                            Inherited from VisionEgg.Textures.TextureStimulusBaseClass
     381                            Default: (determined at runtime)
     382      texture_mag_filter -- OpenGL filter enum (Integer)
     383                            Inherited from VisionEgg.Textures.TextureStimulusBaseClass
     384                            Default: GL_LINEAR (9729)
     385      texture_min_filter -- OpenGL filter enum (Integer)
     386                            Inherited from VisionEgg.Textures.TextureStimulusBaseClass
     387                            Default: (GL enum determined at runtime)
     388      texture_wrap_s     -- OpenGL texture wrap enum (Integer)
     389                            Inherited from VisionEgg.Textures.TextureStimulusBaseClass
     390                            Default: (GL enum determined at runtime)
     391      texture_wrap_t     -- OpenGL texture wrap enum (Integer)
     392                            Inherited from VisionEgg.Textures.TextureStimulusBaseClass
     393                            Default: (GL enum determined at runtime)
     394
     395      Constant Parameters
     396      ===================
     397      internal_format   -- format with which OpenGL uses texture data (OpenGL data type enum) (Integer)
     398                           Default: GL_RGB (6407)
     399      mipmaps_enabled   -- Are mipmaps enabled? (Boolean)
     400                           Default: True
     401      shrink_texture_ok -- Allow automatic shrinking of texture if too big? (Boolean)
     402                           Default: False
     403      """
     404
     405      parameters_and_defaults = {
     406          'on':(True,
     407                ve_types.Boolean),
     408          'contrast':(1.0,
     409                      ve_types.Real),
     410          'center_azimuth':(0.0, # 0=right, 90=right
     411                            ve_types.Real),
     412          'center_elevation':(0.0, # 0=right, 90=up
     413                              ve_types.Real),
     414
     415          # Changing these parameters will cause re-computation of display list (may cause frame skip)
     416          'radius':(1.0,
     417                    ve_types.Real),
     418          'slices':(30,
     419                    ve_types.UnsignedInteger),
     420          'stacks':(30,
     421                    ve_types.UnsignedInteger)}
     422
     423      __slots__ = (
     424          'cached_display_list',
     425          '_cached_radius',
     426          '_cached_slices',
     427          '_cached_stacks',
     428          )
     429
     430 -    def __init__(self,**kw):
     431          VisionEgg.Textures.TextureStimulusBaseClass.__init__(self,**kw)
     432          self.cached_display_list = gl.glGenLists(1) # Allocate a new display list
     433          self.__rebuild_display_list()
     434
     435 -    def __rebuild_display_list(self):
     436          p = self.parameters
     437
     438          s_gain = p.texture.buf_rf - p.texture.buf_lf
     439          t_gain = p.texture.buf_bf - p.texture.buf_tf
     440
     441          s_offs = p.texture.buf_lf
     442          t_offs = p.texture.buf_tf
     443
     444          gl.glNewList(self.cached_display_list,gl.GL_COMPILE)
     445          gl.glBegin(gl.GL_QUADS)
     446
     447          for stack in range(p.stacks):
     448              stack_upper_frac = float(stack+1)/p.stacks
     449              stack_lower_frac = float(stack)/p.stacks
     450              theta_upper = stack_upper_frac * math.pi
     451              theta_lower = stack_lower_frac * math.pi
     452              y_upper = p.radius * math.cos( theta_upper )
     453              w_upper = p.radius * math.sin( theta_upper )
     454              y_lower = p.radius * math.cos( theta_lower )
     455              w_lower = p.radius * math.sin( theta_lower )
     456              for slice in range(p.slices):
     457                  slice_start_frac = float(slice)/p.slices
     458                  slice_stop_frac = float(slice+1)/p.slices
     459                  phi_start = slice_start_frac * 2 * math.pi
     460                  phi_stop = slice_stop_frac * 2 * math.pi
     461                  x_start_upper = w_upper * math.cos(phi_start)
     462                  x_start_lower = w_lower * math.cos(phi_start)
     463                  x_stop_upper = w_upper * math.cos(phi_stop)
     464                  x_stop_lower = w_lower * math.cos(phi_stop)
     465                  z_start_upper = w_upper * math.sin(phi_start)
     466                  z_start_lower = w_lower * math.sin(phi_start)
     467                  z_stop_upper = w_upper * math.sin(phi_stop)
     468                  z_stop_lower = w_lower * math.sin(phi_stop)
     469
     470                  tex_l = slice_start_frac*s_gain+s_offs
     471                  tex_r = slice_stop_frac*s_gain+s_offs
     472                  tex_b = stack_lower_frac*t_gain+t_offs
     473                  tex_t = stack_upper_frac*t_gain+t_offs
     474
     475                  gl.glTexCoord2f(tex_l,tex_t)
     476                  gl.glVertex3f(x_start_upper, y_upper, z_start_upper)
     477
     478                  gl.glTexCoord2f(tex_r,tex_t)
     479                  gl.glVertex3f(x_stop_upper, y_upper, z_stop_upper)
     480
     481                  gl.glTexCoord2f(tex_r,tex_b)
     482                  gl.glVertex3f(x_stop_lower, y_lower, z_stop_lower)
     483
     484                  gl.glTexCoord2f(tex_l,tex_b)
     485                  gl.glVertex3f(x_start_lower, y_lower, z_start_lower)
     486
     487          gl.glEnd()
     488          gl.glEndList()
     489          self._cached_radius = p.radius
     490          self._cached_slices = p.slices
     491          self._cached_stacks = p.stacks
     492
     493 -    def draw(self):
     494          """Redraw the scene on every frame.
     495          """
     496          p = self.parameters
     497
     498          if self._cached_radius != p.radius or self._cached_slices != p.slices or self._cached_stacks != p.stacks:
     499              self.__rebuild_display_list()
     500
     501          if p.on:
     502              # Set OpenGL state variables
     503              gl.glEnable( gl.GL_DEPTH_TEST )
     504              gl.glEnable( gl.GL_TEXTURE_2D )  # Make sure textures are drawn
     505              gl.glEnable( gl.GL_BLEND ) # Contrast control implemented through blending
     506
     507              # All of the contrast control stuff is somewhat arcane and
     508              # not very clear from reading the code, so here is how it
     509              # works in English. (Not that it makes it any more clear!)
     510              #
     511              # In the final "textured fragment" (before being blended
     512              # to the framebuffer), the color values are equal to those
     513              # of the texture (with the exception of pixels around the
     514              # edges which have their amplitudes reduced due to
     515              # anti-aliasing and are intermediate between the color of
     516              # the texture and mid-gray), and the alpha value is set to
     517              # the contrast.  Blending occurs, and by choosing the
     518              # appropriate values for glBlendFunc, adds the product of
     519              # fragment alpha (contrast) and fragment color to the
     520              # product of one minus fragment alpha (contrast) and what
     521              # was already in the framebuffer.
     522
     523              gl.glBlendFunc( gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA )
     524
     525              gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_DECAL)
     526
     527              # clear modelview matrix
     528              gl.glMatrixMode(gl.GL_MODELVIEW)
     529              gl.glPushMatrix()
     530              gl.glColor4f(0.5,0.5,0.5,p.contrast) # Set the polygons' fragment color (implements contrast)
     531
     532              if not self.constant_parameters.mipmaps_enabled:
     533                  if p.texture_min_filter in VisionEgg.Textures.TextureStimulusBaseClass._mipmap_modes:
     534                      raise RuntimeError("Specified a mipmap mode in texture_min_filter, but mipmaps not enabled.")
     535              self.texture_object.set_min_filter( p.texture_min_filter )
     536              self.texture_object.set_mag_filter( p.texture_mag_filter )
     537              self.texture_object.set_wrap_mode_s( p.texture_wrap_s )
     538              self.texture_object.set_wrap_mode_t( p.texture_wrap_t )
     539
     540              # center the texture map
     541              gl.glRotatef(p.center_azimuth,0.0,-1.0,0.0)
     542              gl.glRotatef(p.center_elevation,1.0,0.0,0.0)
     543
     544              gl.glCallList(self.cached_display_list)
     545              gl.glPopMatrix()
     546
     547 -class SphereGrating(VisionEgg.Gratings.LuminanceGratingCommon):
     548      """Map 2D sinusoidal grating onto sphere.
     549
     550      Parameters
     551      ==========
     552      bit_depth                       -- precision with which grating is calculated and sent to OpenGL (UnsignedInteger)
     553                                         Inherited from VisionEgg.Gratings.LuminanceGratingCommon
     554                                         Default: 8
     555      check_texture_size              -- (Boolean)
     556                                         Default: True
     557      contrast                        -- (Real)
     558                                         Default: 1.0
     559      grating_center_azimuth          -- (Real)
     560                                         Default: 0.0
     561      grating_center_elevation        -- (Real)
     562                                         Default: 0.0
     563      ignore_time                     -- (Boolean)
     564                                         Default: False
     565      lowpass_cutoff_cycles_per_texel -- helps prevent spatial aliasing (Real)
     566                                         Default: 0.5
     567      min_filter                      -- OpenGL filter enum (Integer)
     568                                         Default: GL_LINEAR (9729)
     569      num_samples                     -- (UnsignedInteger)
     570                                         Default: 1024
     571      on                              -- (Boolean)
     572                                         Default: True
     573      orientation                     -- (Real)
     574                                         Default: 0.0
     575      phase_at_t0                     -- (Real)
     576                                         Default: 0.0
     577      radius                          -- (Real)
     578                                         Default: 1.0
     579      slices                          -- (UnsignedInteger)
     580                                         Default: 30
     581      spatial_freq_cpd                -- (Real)
     582                                         Default: 0.0277777777778
     583      stacks                          -- (UnsignedInteger)
     584                                         Default: 30
     585      t0_time_sec_absolute            -- (Real)
     586                                         Default: (determined at runtime)
     587      temporal_freq_hz                -- (Real)
     588                                         Default: 5.0
     589      """
     590
     591      parameters_and_defaults = {
     592          'on':(True,
     593                ve_types.Boolean),
     594          'contrast':(1.0,
     595                      ve_types.Real),
     596          'spatial_freq_cpd':(1.0/36.0, # cycles/degree
     597                              ve_types.Real),
     598          'temporal_freq_hz':(5.0, # hz
     599                              ve_types.Real),
     600          't0_time_sec_absolute':(None,
     601                                  ve_types.Real),
     602          'ignore_time':(False, # ignore temporal frequency variable - allow control purely with phase_at_t0
     603                         ve_types.Boolean),
     604          'phase_at_t0':(0.0,  # degrees
     605                         ve_types.Real),
     606          'orientation':(0.0,  # 0=right, 90=up
     607                         ve_types.Real),
     608          'grating_center_azimuth':(0.0, # 0=right, 90=down
     609                                    ve_types.Real),
     610          'grating_center_elevation':(0.0, # 0=right, 90=down
     611                                      ve_types.Real),
     612          'check_texture_size':(True, # slows down drawing but catches errors
     613                                ve_types.Boolean),
     614          'lowpass_cutoff_cycles_per_texel':(0.5,
     615                                             ve_types.Real,
     616                                             'helps prevent spatial aliasing'),
     617          'min_filter':(gl.GL_LINEAR,
     618                        ve_types.Integer,
     619                        "OpenGL filter enum",
     620                        VisionEgg.ParameterDefinition.OPENGL_ENUM),
     621          # changing this parameters causes re-drawing of the texture object and may cause frame skipping
     622          'num_samples':(1024,  # number of spatial samples, should be a power of 2
     623                         ve_types.UnsignedInteger),
     624          # Changing these parameters will cause re-computation of display list (may cause frame skip)
     625          'radius':(1.0,
     626                    ve_types.Real),
     627          'slices':(30,
     628                    ve_types.UnsignedInteger),
     629          'stacks':(30,
     630                    ve_types.UnsignedInteger),
     631          }
     632
     633      __slots__ = (
     634          'texture_object_id',
     635          'cached_display_list_id',
     636          '_cached_num_samples',
     637          '_cached_radius',
     638          '_cached_slices',
     639          '_cached_stacks',
     640          )
     641
     642 -    def __init__(self,**kw):
     643          VisionEgg.Gratings.LuminanceGratingCommon.__init__(self,**kw)
     644
     645          if self.parameters.t0_time_sec_absolute is None:
     646              self.parameters.t0_time_sec_absolute = VisionEgg.time_func()
     647
     648          self.texture_object_id = gl.glGenTextures(1) # Allocate a new texture object
     649          self.__rebuild_texture_object()
     650
     651          self.cached_display_list_id = gl.glGenLists(1) # Allocate a new display list
     652          self.__rebuild_display_list()
     653
     654 -    def __rebuild_texture_object(self):
     655          gl.glBindTexture(gl.GL_TEXTURE_1D,self.texture_object_id)
     656          p = self.parameters # shorthand
     657
     658          # Do error-checking on texture to make sure it will load
     659          max_dim = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE)
     660          if p.num_samples > max_dim:
     661              raise VisionEgg.Gratings.NumSamplesTooLargeError("Grating num_samples too large for video system.\nOpenGL reports maximum size of %d"%(max_dim,))
     662
     663          self.calculate_bit_depth_dependencies()
     664
     665          l = 0.0
     666          r = 360.0
     667
     668          mipmap_level = 0
     669          this_mipmap_level_num_samples = p.num_samples
     670          while this_mipmap_level_num_samples >= 1:
     671              inc = 360.0/float(this_mipmap_level_num_samples) # degrees per pixel
     672              cycles_per_texel = p.spatial_freq_cpd * inc
     673              if cycles_per_texel < p.lowpass_cutoff_cycles_per_texel: # sharp cutoff lowpass filter
     674                  # below cutoff frequency - draw sine wave
     675                  if p.ignore_time:
     676                      phase = p.phase_at_t0
     677                  else:
     678                      t_var = VisionEgg.time_func() - p.t0_time_sec_absolute
     679                      phase = t_var*p.temporal_freq_hz*360.0 + p.phase_at_t0
     680                  floating_point_sin = Numeric.sin(2.0*math.pi*p.spatial_freq_cpd*Numeric.arange(l,r,inc,'d')-(phase/180.0*math.pi))*0.5*p.contrast+0.5
     681                  floating_point_sin = Numeric.clip(floating_point_sin,0.0,1.0) # allow square wave generation if contrast > 1
     682                  texel_data = (floating_point_sin*self.max_int_val).astype(self.numpy_dtype).tostring()
     683              else:
     684                  # above cutoff frequency - blank
     685                  texel_data = (self.max_int_val*0.5)*Numeric.ones((this_mipmap_level_num_samples,),self.numpy_dtype)
     686
     687              if p.check_texture_size:
     688                  # Because the MAX_TEXTURE_SIZE method is insensitive to the current
     689                  # state of the video system, another check must be done using
     690                  # "proxy textures".
     691                  gl.glTexImage1D(gl.GL_PROXY_TEXTURE_1D,  # target
     692                                  mipmap_level,            # level
     693                                  self.gl_internal_format, # video RAM internal format: RGB
     694                                  this_mipmap_level_num_samples,        # width
     695                                  0,                       # border
     696                                  self.format,             # format of image data
     697                                  self.gl_type,            # type of image data
     698                                  texel_data)              # texel data
     699                  if gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_1D,0,gl.GL_TEXTURE_WIDTH) == 0:
     700                      raise NumSamplesTooLargeError("Grating num_samples is too wide for your video system!")
     701
     702              # If we got here, it worked and we can load the texture for real.
     703              gl.glTexImage1D(gl.GL_TEXTURE_1D,        # target
     704                              mipmap_level,            # level
     705                              self.gl_internal_format, # video RAM internal format: RGB
     706                              this_mipmap_level_num_samples,        # width
     707                              0,                       # border
     708                              self.format,             # format of image data
     709                              self.gl_type,            # type of image data
     710                              texel_data)              # texel data
     711
     712              # prepare for next mipmap level
     713              this_mipmap_level_num_samples = this_mipmap_level_num_samples/2 # integer division
     714              mipmap_level += 1
     715
     716          # Set some texture object defaults
     717          gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_WRAP_S,gl.GL_REPEAT)
     718          gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_WRAP_T,gl.GL_REPEAT)
     719          gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MAG_FILTER,gl.GL_LINEAR)
     720          gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MIN_FILTER,p.min_filter)
     721          self._cached_num_samples = p.num_samples
     722
     723 -    def __rebuild_display_list(self):
     724          gl.glNewList(self.cached_display_list_id,gl.GL_COMPILE)
     725
     726          p = self.parameters
     727          gl.glBegin(gl.GL_QUADS)
     728
     729          for stack in range(p.stacks):
     730              stack_upper_frac = float(stack+1)/p.stacks
     731              stack_lower_frac = float(stack)/p.stacks
     732              theta_upper = stack_upper_frac * math.pi
     733              theta_lower = stack_lower_frac * math.pi
     734              y_upper = p.radius * math.cos( theta_upper )
     735              w_upper = p.radius * math.sin( theta_upper )
     736              y_lower = p.radius * math.cos( theta_lower )
     737              w_lower = p.radius * math.sin( theta_lower )
     738              for slice in range(p.slices):
     739                  slice_start_frac = float(slice)/p.slices
     740                  slice_stop_frac = float(slice+1)/p.slices
     741                  phi_start = slice_start_frac * 2 * math.pi
     742                  phi_stop = slice_stop_frac * 2 * math.pi
     743                  x_start_upper = w_upper * math.cos(phi_start)
     744                  x_start_lower = w_lower * math.cos(phi_start)
     745                  x_stop_upper = w_upper * math.cos(phi_stop)
     746                  x_stop_lower = w_lower * math.cos(phi_stop)
     747                  z_start_upper = w_upper * math.sin(phi_start)
     748                  z_start_lower = w_lower * math.sin(phi_start)
     749                  z_stop_upper = w_upper * math.sin(phi_stop)
     750                  z_stop_lower = w_lower * math.sin(phi_stop)
     751
     752                  tex_l = slice_start_frac
     753                  tex_r = slice_stop_frac
     754                  tex_b = 0.0#stack_lower_frac
     755                  tex_t = 1.0#stack_upper_frac
     756
     757                  gl.glTexCoord2f(tex_l,tex_t)
     758                  gl.glVertex3f(x_start_upper, y_upper, z_start_upper)
     759
     760                  gl.glTexCoord2f(tex_r,tex_t)
     761                  gl.glVertex3f(x_stop_upper, y_upper, z_stop_upper)
     762
     763                  gl.glTexCoord2f(tex_r,tex_b)
     764                  gl.glVertex3f(x_stop_lower, y_lower, z_stop_lower)
     765
     766                  gl.glTexCoord2f(tex_l,tex_b)
     767                  gl.glVertex3f(x_start_lower, y_lower, z_start_lower)
     768
     769          gl.glEnd()
     770          gl.glEndList()
     771          self._cached_radius = p.radius
     772          self._cached_slices = p.slices
     773          self._cached_stacks = p.stacks
     774
     775 -    def draw(self):
     776          """Redraw the scene on every frame.
     777          """
     778          p = self.parameters
     779
     780          if self._cached_radius != p.radius or self._cached_slices != p.slices or self._cached_stacks != p.stacks:
     781              self.__rebuild_display_list()
     782
     783          if self._cached_num_samples != p.num_samples:
     784              self.__rebuild_texture_object()
     785
     786          if p.on:
     787              if p.bit_depth != self.cached_bit_depth:
     788                  self.calculate_bit_depth_dependencies()
     789              # Set OpenGL state variables
     790              gl.glEnable( gl.GL_DEPTH_TEST )
     791              gl.glEnable( gl.GL_TEXTURE_1D )  # Make sure textures are drawn
     792              gl.glDisable( gl.GL_TEXTURE_2D )
     793              gl.glDisable( gl.GL_BLEND )
     794
     795              gl.glBindTexture(gl.GL_TEXTURE_1D,self.texture_object_id)
     796              gl.glTexParameteri(gl.GL_TEXTURE_1D,gl.GL_TEXTURE_MIN_FILTER,p.min_filter)
     797
     798              l = 0.0
     799              r = 360.0
     800
     801              mipmap_level = 0
     802              this_mipmap_level_num_samples = p.num_samples
     803              while this_mipmap_level_num_samples >= 1:
     804                  inc = 360.0/float(this_mipmap_level_num_samples)# degrees per pixel
     805                  cycles_per_texel = p.spatial_freq_cpd * inc
     806                  if cycles_per_texel < p.lowpass_cutoff_cycles_per_texel: # sharp cutoff lowpass filter
     807                      if p.ignore_time:
     808                          phase = p.phase_at_t0
     809                      else:
     810                          t_var = VisionEgg.time_func() - p.t0_time_sec_absolute
     811                          phase = t_var*p.temporal_freq_hz*360.0 + p.phase_at_t0
     812                      floating_point_sin = Numeric.sin(2.0*math.pi*p.spatial_freq_cpd*Numeric.arange(l,r,inc,'d')-(phase/180.0*math.pi))*0.5*p.contrast+0.5
     813                      floating_point_sin = Numeric.clip(floating_point_sin,0.0,1.0) # allow square wave generation if contrast > 1
     814                      texel_data = (floating_point_sin*self.max_int_val).astype(self.numpy_dtype).tostring()
     815                  else:
     816                      blank = 0.5*Numeric.ones((this_mipmap_level_num_samples,),'d')
     817                      texel_data = (blank*self.max_int_val).astype(self.numpy_dtype).tostring()
     818
     819                  gl.glTexSubImage1D(gl.GL_TEXTURE_1D,           # target
     820                                  mipmap_level,                  # level
     821                                  0,                             # x offset
     822                                  this_mipmap_level_num_samples, # width
     823                                  self.format,                   # data format
     824                                  self.gl_type,                  # data type
     825                                  texel_data)
     826
     827                  # prepare for next mipmap level
     828                  this_mipmap_level_num_samples = this_mipmap_level_num_samples/2 # integer division
     829                  mipmap_level += 1
     830
     831              gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_REPLACE)
     832
     833              # clear modelview matrix
     834              gl.glMatrixMode(gl.GL_MODELVIEW)
     835              gl.glPushMatrix()
     836              # center the grating
     837              gl.glRotatef(p.grating_center_azimuth,0.0,-1.0,0.0)
     838              gl.glRotatef(p.grating_center_elevation,1.0,0.0,0.0)
     839
     840              # do the orientation
     841              gl.glRotatef(p.orientation,0.0,0.0,1.0)
     842
     843              gl.glCallList(self.cached_display_list_id)
     844
     845              gl.glDisable( gl.GL_TEXTURE_1D )
     846              gl.glPopMatrix()
     847
     848 -class SphereWindow(VisionEgg.Gratings.LuminanceGratingCommon):
     849      """This draws an opaque sphere with a single window in it.
     850
     851      This is useful when you need to have a viewport on a 3D scene.
     852
     853      Parameters
     854      ==========
     855      bit_depth                     -- precision with which grating is calculated and sent to OpenGL (UnsignedInteger)
     856                                       Inherited from VisionEgg.Gratings.LuminanceGratingCommon
     857                                       Default: 8
     858      num_s_samples                 -- (UnsignedInteger)
     859                                       Default: 512
     860      num_t_samples                 -- (UnsignedInteger)
     861                                       Default: 512
     862      on                            -- (Boolean)
     863                                       Default: True
     864      opaque_color                  -- (Sequence4 of Real)
     865                                       Default: (0.5, 0.5, 0.5, 0.0)
     866      radius                        -- (Real)
     867                                       Default: 1.0
     868      slices                        -- (UnsignedInteger)
     869                                       Default: 30
     870      stacks                        -- (UnsignedInteger)
     871                                       Default: 30
     872      window_center_azimuth         -- (Real)
     873                                       Default: 0.0
     874      window_center_elevation       -- (Real)
     875                                       Default: 0.0
     876      window_shape                  -- can be 'circle', 'gaussian', or 'lat-long rectangle' (String)
     877                                       Default: gaussian
     878      window_shape_parameter2       -- (currently only used for) height of lat-long rectangle (in degrees) (Real)
     879                                       Default: 30.0
     880      window_shape_radius_parameter -- radius of circle, sigma of gaussian, width of lat-long rectangle (in degrees) (Real)
     881                                       Default: 36.0
     882      """
     883
     884      parameters_and_defaults = {
     885          'on':(True,
     886                ve_types.Boolean),
     887          'window_center_elevation':(0.0,
     888                                     ve_types.Real),
     889          'window_center_azimuth':(0.0,
     890                                   ve_types.Real),
     891          'opaque_color':((0.5,0.5,0.5,0.0),
     892                          ve_types.Sequence4(ve_types.Real)),
     893          # changing these parameters causes re-drawing of the texture object and may cause frame skipping
     894          'window_shape':('gaussian', # can be 'circle' or 'gaussian'
     895                          ve_types.String,
     896                          "can be 'circle', 'gaussian', or 'lat-long rectangle'",
     897                          ),
     898          'window_shape_radius_parameter':(36.0,
     899                                           ve_types.Real,
     900                                           'radius of circle, sigma of gaussian, width of lat-long rectangle (in degrees)',
     901                                           ),
     902          'window_shape_parameter2':(30.0,
     903                                     ve_types.Real,
     904                                     '(currently only used for) height of lat-long rectangle (in degrees)',
     905                                     ),
     906          'num_s_samples':(512,  # number of horizontal spatial samples, should be a power of 2
     907                           ve_types.UnsignedInteger),
     908          'num_t_samples':(512,  # number of vertical spatial samples, should be a power of 2
     909                           ve_types.UnsignedInteger),
     910          # Changing these parameters will cause re-computation of display list (may cause frame skip)
     911          'radius':(1.0, # XXX could modify code below to use scaling, thus avoiding need for recomputation
     912                    ve_types.Real),
     913          'slices':(30,
     914                    ve_types.UnsignedInteger),
     915          'stacks':(30,
     916                    ve_types.UnsignedInteger),
     917          }
     918
     919      __slots__ = (
     920          'texture_object_id',
     921          'windowed_display_list_id',
     922          'opaque_display_list_id',
     923          '_cached_window_shape',
     924          '_cached_shape_radius_parameter',
     925          '_cached_shape_parameter2',
     926          '_cached_num_s_samples',
     927          '_cached_num_t_samples',
     928          '_cached_radius',
     929          '_cached_slices',
     930          '_cached_stacks',
     931          '_texture_s_is_azimuth',
     932          )
     933
     934 -    def __init__(self, **kw):
     935          VisionEgg.Gratings.LuminanceGratingCommon.__init__(self, **kw )
     936
     937          p = self.parameters
     938
     939          # set self._texture_s_is_azimuth in advance
     940          if p.window_shape == 'lat-long rectangle':
     941              self._texture_s_is_azimuth = True
     942          else:
     943              self._texture_s_is_azimuth = False
     944
     945          self.texture_object_id = gl.glGenTextures(1)
     946          self.__rebuild_texture_object()
     947
     948          self.windowed_display_list_id = gl.glGenLists(1) # Allocate a new display list
     949          self.opaque_display_list_id = gl.glGenLists(1) # Allocate a new display list
     950          self.__rebuild_display_lists()
     951
     952 -    def __rebuild_texture_object(self):
     953          gl.glBindTexture(gl.GL_TEXTURE_2D,self.texture_object_id)
     954          p = self.parameters
     955
     956          # Do error-checking on texture to make sure it will load
     957          max_dim = gl.glGetIntegerv(gl.GL_MAX_TEXTURE_SIZE)
     958          if p.num_s_samples > max_dim:
     959              raise VisionEgg.Gratings.NumSamplesTooLargeError("SphereWindow num_s_samples too large for video system.\nOpenGL reports maximum size of %d"%(max_dim,))
     960          if p.num_t_samples > max_dim:
     961              raise VisionEgg.Gratings.NumSamplesTooLargeError("SphereWindow num_t_samples too large for video system.\nOpenGL reports maximum size of %d"%(max_dim,))
     962
     963          self.calculate_bit_depth_dependencies()
     964          self.gl_internal_format = gl.GL_ALPHA # change from luminance to alpha
     965          self.format = gl.GL_ALPHA
     966
     967          # texture coordinates are Mercator: (determined when building display list)
     968          #   s: x within sphere
     969          #   t: z within sphere
     970
     971          if p.window_shape == 'circle':
     972              if self._texture_s_is_azimuth:
     973                  self.__rebuild_display_lists()
     974
     975              # XXX this is aliased
     976              s_axis = (Numeric.arange(p.num_s_samples)/float(p.num_s_samples)-0.5)**2
     977              t_axis = (Numeric.arange(p.num_t_samples)/float(p.num_t_samples)-0.5)**2
     978              mask = s_axis[Numeric.NewAxis,:] + t_axis[:,Numeric.NewAxis]
     979              angle_deg = min(180,p.window_shape_radius_parameter) # clip angle
     980              cartesian_radius = 0.5*math.sin(p.window_shape_radius_parameter/180.0*math.pi)
     981              floating_point_window = Numeric.less(mask,cartesian_radius**2)
     982          elif p.window_shape == 'gaussian':
     983              if self._texture_s_is_azimuth:
     984                  self.__rebuild_display_lists()
     985
     986              MIN_EXP = -745.0
     987              MAX_EXP =  709.0
     988
     989              s = Numeric.arange(0.0,p.num_s_samples,1.0,'f')/p.num_s_samples
     990              t = Numeric.arange(0.0,p.num_t_samples,1.0,'f')/p.num_t_samples
     991              sigma_normalized = p.window_shape_radius_parameter / 90.0 * 0.5
     992
     993              check_s = -((s-0.5)**2/(2.0*sigma_normalized**2))
     994              try:
     995                  # some platforms raise OverflowError when doing this on small numbers
     996                  val_s = Numeric.exp( check_s )
     997              except OverflowError:
     998                  check_s = Numeric.clip(check_s,MIN_EXP,MAX_EXP)
     999                  val_s = Numeric.exp( check_s )
    1000
    1001              check_t = -((t-0.5)**2/(2.0*sigma_normalized**2))
    1002              try:
    1003                  val_t = Numeric.exp( check_t )
    1004              except OverflowError:
    1005                  check_t = Numeric.clip(check_t,MIN_EXP,MAX_EXP)
    1006                  val_t = Numeric.exp( check_t )
    1007              floating_point_window = Numeric.outerproduct(val_t,val_s)
    1008          elif  p.window_shape == 'lat-long rectangle':
    1009              if not self._texture_s_is_azimuth:
    1010                  self.__rebuild_display_lists()
    1011
    1012              # s coordinate represents -90 to +90 degrees (azimuth).
    1013              s_axis = (Numeric.arange(p.num_s_samples)/float(p.num_s_samples)-0.5)*180
    1014              s_axis = Numeric.less( abs(s_axis), p.window_shape_radius_parameter*0.5 )
    1015
    1016              # t coordinate represents height.
    1017              # Convert angle to height.
    1018              angle_deg = min(90,p.window_shape_parameter2*0.5) # clip angle
    1019              desired_height = math.sin(angle_deg/180.0*math.pi)*0.5
    1020              t_axis = Numeric.arange(p.num_t_samples)/float(p.num_t_samples)-0.5
    1021              t_axis = Numeric.less(abs(t_axis),desired_height)
    1022              floating_point_window = Numeric.outerproduct(t_axis,s_axis)
    1023          else:
    1024              raise RuntimeError('Unknown window_shape "%s"'%(p.window_shape,))
    1025          texel_data = (floating_point_window * self.max_int_val).astype(self.numpy_dtype).tostring()
    1026
    1027          # Because the MAX_TEXTURE_SIZE method is insensitive to the current
    1028          # state of the video system, another check must be done using
    1029          # "proxy textures".
    1030          gl.glTexImage2D(gl.GL_PROXY_TEXTURE_2D,      # target
    1031                       0,                              # mipmap_level
    1032                       self.gl_internal_format,        # video RAM internal format
    1033                       p.num_s_samples,  # width
    1034                       p.num_t_samples,  # height
    1035                       0,                              # border
    1036                       self.format,                    # format of image data
    1037                       self.gl_type,                   # type of image data
    1038                       texel_data)                     # texel data
    1039          if (gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_2D, # Need PyOpenGL >= 2.0
    1040                                          0,
    1041                                          gl.GL_TEXTURE_WIDTH) == 0) or (
    1042              gl.glGetTexLevelParameteriv(gl.GL_PROXY_TEXTURE_2D,
    1043                                          0,
    1044                                          gl.GL_TEXTURE_HEIGHT) == 0):
    1045              raise VisionEgg.Gratings.NumSamplesTooLargeError("SphereWindow num_s_samples or num_t_samples is too large for your video system!")
    1046
    1047          gl.glTexImage2D(gl.GL_TEXTURE_2D,      # target
    1048                          0,                              # mipmap_level
    1049                          self.gl_internal_format,        # video RAM internal format
    1050                          p.num_s_samples,  # width
    1051                          p.num_t_samples,  # height
    1052                          0,                              # border
    1053                          self.format,                    # format of image data
    1054                          self.gl_type,                   # type of image data
    1055                          texel_data)                     # texel data
    1056
    1057          # Set some texture object defaults
    1058          gl.glTexParameteri(gl.GL_TEXTURE_2D,gl.GL_TEXTURE_WRAP_S,gl.GL_CLAMP_TO_EDGE)
    1059          gl.glTexParameteri(gl.GL_TEXTURE_2D,gl.GL_TEXTURE_WRAP_T,gl.GL_CLAMP_TO_EDGE)
    1060          gl.glTexParameteri(gl.GL_TEXTURE_2D,gl.GL_TEXTURE_MAG_FILTER,gl.GL_LINEAR)
    1061          gl.glTexParameteri(gl.GL_TEXTURE_2D,gl.GL_TEXTURE_MIN_FILTER,gl.GL_LINEAR)
    1062
    1063          self._cached_window_shape = p.window_shape
    1064          self._cached_shape_radius_parameter = p.window_shape_radius_parameter
    1065          self._cached_shape_parameter2 = p.window_shape_parameter2
    1066          self._cached_num_s_samples = p.num_s_samples
    1067          self._cached_num_t_samples = p.num_t_samples
    1068
    1069 -    def __rebuild_display_lists(self):
    1070          gl.glMatrixMode(gl.GL_MODELVIEW)
    1071          gl.glPushMatrix()
    1072
    1073          p = self.parameters
    1074
    1075          if p.window_shape == 'lat-long rectangle':
    1076              self._texture_s_is_azimuth = True
    1077          else:
    1078              self._texture_s_is_azimuth = False
    1079
    1080          gl.glNewList(self.windowed_display_list_id,gl.GL_COMPILE)
    1081
    1082          gl.glBegin(gl.GL_QUADS)
    1083
    1084          for stack in range(p.stacks):
    1085              stack_upper_frac = float(stack+1)/p.stacks
    1086              stack_lower_frac = float(stack)/p.stacks
    1087              theta_upper = stack_upper_frac * math.pi
    1088              theta_lower = stack_lower_frac * math.pi
    1089              y_upper = p.radius * math.cos( theta_upper )
    1090              w_upper = p.radius * math.sin( theta_upper )
    1091              y_lower = p.radius * math.cos( theta_lower )
    1092              w_lower = p.radius * math.sin( theta_lower )
    1093              for slice in range(p.slices/2,p.slices): # only do half of sphere (other half has no window)
    1094                  slice_start_frac = float(slice)/p.slices
    1095                  slice_stop_frac = float(slice+1)/p.slices
    1096                  phi_start = slice_start_frac * 2 * math.pi
    1097                  phi_stop = slice_stop_frac * 2 * math.pi
    1098                  x_start_upper = w_upper * math.cos(phi_start)
    1099                  x_start_lower = w_lower * math.cos(phi_start)
    1100                  x_stop_upper = w_upper * math.cos(phi_stop)
    1101                  x_stop_lower = w_lower * math.cos(phi_stop)
    1102                  z_start_upper = w_upper * math.sin(phi_start)
    1103                  z_start_lower = w_lower * math.sin(phi_start)
    1104                  z_stop_upper = w_upper * math.sin(phi_stop)
    1105                  z_stop_lower = w_lower * math.sin(phi_stop)
    1106
    1107                  o = 0.5
    1108                  g = 0.5 / p.radius
    1109
    1110                  if self._texture_s_is_azimuth:
    1111                      tex_s_start = slice_start_frac*2-1
    1112                      tex_s_stop = slice_stop_frac*2-1
    1113                  else:
    1114                      tex_s_start = x_start_upper*g+o
    1115                      tex_s_stop = x_stop_upper*g+o
    1116
    1117                  gl.glTexCoord2f(tex_s_start,y_upper*g+o)
    1118                  gl.glVertex3f(x_start_upper, y_upper, z_start_upper)
    1119
    1120                  gl.glTexCoord2f(tex_s_stop,y_upper*g+o)
    1121                  gl.glVertex3f(x_stop_upper, y_upper, z_stop_upper)
    1122
    1123                  gl.glTexCoord2f(tex_s_stop,y_lower*g+o)
    1124                  gl.glVertex3f(x_stop_lower, y_lower, z_stop_lower)
    1125
    1126                  gl.glTexCoord2f(tex_s_start,y_lower*g+o)
    1127                  gl.glVertex3f(x_start_lower, y_lower, z_start_lower)
    1128
    1129          gl.glEnd()
    1130          gl.glEndList()
    1131
    1132          gl.glNewList(self.opaque_display_list_id,gl.GL_COMPILE)
    1133
    1134          gl.glBegin(gl.GL_QUADS)
    1135
    1136          for stack in range(p.stacks):
    1137              stack_upper_frac = float(stack+1)/p.stacks
    1138              stack_lower_frac = float(stack)/p.stacks
    1139              theta_upper = stack_upper_frac * math.pi
    1140              theta_lower = stack_lower_frac * math.pi
    1141              y_upper = p.radius * math.cos( theta_upper )
    1142              w_upper = p.radius * math.sin( theta_upper )
    1143              y_lower = p.radius * math.cos( theta_lower )
    1144              w_lower = p.radius * math.sin( theta_lower )
    1145              for slice in range(p.slices/2): # half of sphere with no window
    1146                  slice_start_frac = float(slice)/p.slices
    1147                  slice_stop_frac = float(slice+1)/p.slices
    1148                  phi_start = slice_start_frac * 2 * math.pi
    1149                  phi_stop = slice_stop_frac * 2 * math.pi
    1150                  x_start_upper = w_upper * math.cos(phi_start)
    1151                  x_start_lower = w_lower * math.cos(phi_start)
    1152                  x_stop_upper = w_upper * math.cos(phi_stop)
    1153                  x_stop_lower = w_lower * math.cos(phi_stop)
    1154                  z_start_upper = w_upper * math.sin(phi_start)
    1155                  z_start_lower = w_lower * math.sin(phi_start)
    1156                  z_stop_upper = w_upper * math.sin(phi_stop)
    1157                  z_stop_lower = w_lower * math.sin(phi_stop)
    1158
    1159                  gl.glVertex3f(x_start_upper, y_upper, z_start_upper)
    1160
    1161                  gl.glVertex3f(x_stop_upper, y_upper, z_stop_upper)
    1162
    1163                  gl.glVertex3f(x_stop_lower, y_lower, z_stop_lower)
    1164
    1165                  gl.glVertex3f(x_start_lower, y_lower, z_start_lower)
    1166
    1167          gl.glEnd()
    1168          gl.glEndList()
    1169          self._cached_radius = p.radius
    1170          self._cached_slices = p.slices
    1171          self._cached_stacks = p.stacks
    1172          gl.glPopMatrix()
    1173
    1174 -    def draw(self):
    1175          """Redraw the scene on every frame.
    1176          """
    1177          p = self.parameters
    1178
    1179          if self._cached_radius != p.radius or self._cached_slices != p.slices or self._cached_stacks != p.stacks:
    1180              self.__rebuild_display_lists()
    1181
    1182          if self._cached_window_shape != p.window_shape or self._cached_shape_radius_parameter != p.window_shape_radius_parameter:
    1183              self.__rebuild_texture_object()
    1184
    1185          if p.window_shape == 'lat-long rectangle' and self._cached_shape_parameter2 != p.window_shape_parameter2:
    1186              self.__rebuild_texture_object()
    1187
    1188          if self._cached_num_s_samples != p.num_s_samples or self._cached_num_t_samples != p.num_t_samples:
    1189              self.__rebuild_texture_object()
    1190
    1191          if p.on:
    1192              #gl.glPolygonMode( gl.GL_FRONT_AND_BACK, gl.GL_LINE )
    1193              if p.bit_depth != self.cached_bit_depth:
    1194                  self.calculate_bit_depth_dependencies()
    1195                  self.gl_internal_format = gl.GL_ALPHA # change from luminance to alpha
    1196                  self.format = gl.GL_ALPHA
    1197              # Set OpenGL state variables
    1198              gl.glEnable( gl.GL_DEPTH_TEST )
    1199              gl.glEnable( gl.GL_TEXTURE_2D )
    1200              gl.glEnable( gl.GL_BLEND )
    1201
    1202              gl.glBlendFunc( gl.GL_ONE_MINUS_SRC_ALPHA, gl.GL_SRC_ALPHA ) # alpha 1.0 = transparent
    1203
    1204              gl.glBindTexture(gl.GL_TEXTURE_2D,self.texture_object_id)
    1205              gl.glColor4f( *p.opaque_color )
    1206              gl.glTexEnvi(gl.GL_TEXTURE_ENV, gl.GL_TEXTURE_ENV_MODE, gl.GL_REPLACE)
    1207
    1208              # clear modelview matrix
    1209              gl.glMatrixMode(gl.GL_MODELVIEW)
    1210              gl.glPushMatrix()
    1211
    1212              # do the window position
    1213              gl.glRotatef(p.window_center_azimuth,0.0,-1.0,0.0)
    1214              gl.glRotatef(p.window_center_elevation,1.0,0.0,0.0)
    1215
    1216              gl.glCallList(self.windowed_display_list_id)
    1217              gl.glCallList(self.opaque_display_list_id)
    1218              gl.glPopMatrix()
    1219